(** * Universal morphisms *)
Require Import Category.Core Functor.Core.
Require Import Category.Dual Functor.Dual.
Require Import Category.Objects.
Require Import InitialTerminalCategory.Core InitialTerminalCategory.Functors.
Require Comma.Core.
Local Set Warnings Append "-notation-overridden". (* work around bug #5567, https://coq.inria.fr/bugs/show_bug.cgi?id=5567, notation-overridden,parsing should not trigger for only printing notations *)
Import Comma.Core.
Local Set Warnings Append "notation-overridden".
Require Import Trunc Types.Sigma HoTT.Tactics.
Require Import Basics.Tactics.

Set Universe Polymorphism.
Set Implicit Arguments.
Generalizable All Variables.
Set Asymmetric Patterns.

Local Open Scope morphism_scope.

Section UniversalMorphism.
  (** Quoting Wikipedia:

      Suppose that [U : D → C] is a functor from a category [D] to a
      category [C], and let [X] be an object of [C].  Consider the
      following dual (opposite) notions: *)

  Local Ltac univ_hprop_t UniversalProperty :=
    apply @istrunc_succ in UniversalProperty;
    eapply @istrunc_sigma;
    first [ intro;
            simpl;
            match goal with
              | [ |- context[?m o 1] ]
                => simpl rewrite (right_identity _ _ _ m)
              | [ |- context[1 o ?m] ]
                => simpl rewrite (left_identity _ _ _ m)
            end;
            assumption
          | by typeclasses eauto ].

  (** ** Initial morphisms *)
  Section InitialMorphism.
    (** *** Definition *)
    Variables C D : PreCategory.

    Variable X : C.
    Variable U : Functor D C.
    (** An initial morphism from [X] to [U] is an initial object in
        the category [(X ↓ U)] of morphisms from [X] to [U].  In other
        words, it consists of a pair [(A, φ)] where [A] is an object
        of [D] and [φ: X → U A] is a morphism in [C], such that the
        following initial property is satisfied:

       - Whenever [Y] is an object of [D] and [f : X → U Y] is a
         morphism in [C], then there exists a unique morphism [g : A
         → Y] such that the following diagram commutes:

<<
             φ
         X -----> U A       A
          \        .        .
            \      . U g    . g
           f  \    .        .
               ↘   ↓        ↓
                 U Y        Y
>>
       *)

    Definition IsInitialMorphism (Ap : object (X / U)) :=
      IsInitialObject (X / U) Ap.

    (** *** Introduction rule *)
    Section IntroductionAbstractionBarrier.
      Definition Build_IsInitialMorphism
                 (*(Ap : Object (X ↓ U))*)
                 (A : D)(* := CCO_b Ap*)
                 (p : morphism C X (U A))(*:= CCO_f Ap*)
                 (Ap := CommaCategory.Build_object !X U tt A p)
                 (UniversalProperty
                  : forall (A' : D) (p' : morphism C X (U A')),
                      Contr { m : morphism D A A'
                            | U _1 m o p = p' })
      : IsInitialMorphism Ap.
      Proof.
        intro x.
        specialize (UniversalProperty (CommaCategory.b x) (CommaCategory.f x)).
        (** We want to preserve the computation rules for the morphisms, even though they're unique up to unique isomorphism. *)
        eapply istrunc_equiv_istrunc.
        - apply CommaCategory.issig_morphism.
        - apply contr_inhabited_hprop.
          + abstract univ_hprop_t UniversalProperty.
          + (exists tt).
            (exists (@center _ UniversalProperty).1).
            abstract (progress rewrite ?right_identity, ?left_identity;
                      exact (@center _ UniversalProperty).2).
      Defined.

      Definition Build_IsInitialMorphism_curried
                 (A : D)
                 (p : morphism C X (U A))
                 (Ap := CommaCategory.Build_object !X U tt A p)
                 (m : forall (A' : D) (p' : morphism C X (U A')),
                        morphism D A A')
                 (H : forall (A' : D) (p' : morphism C X (U A')),
                        U _1 (m A' p') o p = p')
                 (H' : forall (A' : D) (p' : morphism C X (U A')) m',
                         U _1 m' o p = p'
                         -> m A' p' = m')
      : IsInitialMorphism Ap
        := Build_IsInitialMorphism
             A
             p
             (fun A' p' =>
                {| center := (m A' p'; H A' p');
                   contr m' := path_sigma
                                 _
                                 (m A' p'; H A' p')
                                 m'
                                 (H' A' p' m'.1 m'.2)
                                 (center _) |}).

      (** Projections from nested sigmas are currently rather slow.  We should just be able to do

<<
      Definition Build_IsInitialMorphism_uncurried
                 (univ
                  : { A : D
                    | { p : morphism C X (U A)
                       | let Ap := CommaCategory.Build_object !X U tt A p in
                         forall (A' : D) (p' : morphism C X (U A')),
                           { m : morphism D A A'
                           | { H : U _1 m o p = p'
                             | forall m',
                                 U _1 m' o p = p'
                                 -> m = m' }}}})
        := @Build_IsInitialMorphism_curried
             (univ.1)
             (univ.2.1)
             (fun A' p' => (univ.2.2 A' p').1)
             (fun A' p' => (univ.2.2 A' p').2.1)
             (fun A' p' => (univ.2.2 A' p').2.2).
>>

      But that's currently too slow.  (About 6-8 seconds, on my machine.)  So instead we factor out all of the type parts by hand, and then apply them after. *)

      Let make_uncurried A' B' C' D' E'0
          (E'1 : forall a a' b b' (c : C' a a'), D' a a' b b' c -> E'0 a a' -> Type)
          (E' : forall a a' b b' (c : C' a a'), D' a a' b b' c -> E'0 a a' -> Type)
          F'
          (f : forall (a : A')
                      (b : B' a)
                      (c : forall (a' : A') (b' : B' a'),
                             C' a a')
                      (d : forall (a' : A') (b' : B' a'),
                             D' a a' b b' (c a' b'))
                      (e : forall (a' : A') (b' : B' a')
                                  (e0 : E'0 a a')
                                  (e1 : E'1 a a' b b' (c a' b') (d a' b') e0),
                             E' a a' b b' (c a' b') (d a' b') e0),
                 F' a b)
          (univ
           : { a : A'
             | { b : B' a
               | forall (a' : A') (b' : B' a'),
                   { c : C' a a'
                   | { d : D' a a' b b' c
                     | forall (e0 : E'0 a a')
                              (e1 : E'1 a a' b b' c d e0),
                         E' a a' b b' c d e0 }}}})
      : F' univ.1 univ.2.1
        := f
             (univ.1)
             (univ.2.1)
             (fun A' p' => (univ.2.2 A' p').1)
             (fun A' p' => (univ.2.2 A' p').2.1)
             (fun A' p' => (univ.2.2 A' p').2.2).

      Definition Build_IsInitialMorphism_uncurried
      : forall (univ
                : { A : D
                  | { p : morphism C X (U A)
                    | let Ap := CommaCategory.Build_object !X U tt A p in
                      forall (A' : D) (p' : morphism C X (U A')),
                        { m : morphism D A A'
                        | { H : U _1 m o p = p'
                          | forall m',
                              U _1 m' o p = p'
                              -> m = m' }}}}),
          IsInitialMorphism (CommaCategory.Build_object !X U tt univ.1 univ.2.1)
        := @make_uncurried
             _ _ _ _ _ _ _ _
             (@Build_IsInitialMorphism_curried).
    End IntroductionAbstractionBarrier.

    Global Arguments Build_IsInitialMorphism : simpl never.
    Global Arguments Build_IsInitialMorphism_curried : simpl never.
    Global Arguments Build_IsInitialMorphism_uncurried : simpl never.

    (** *** Elimination rule *)
    Section EliminationAbstractionBarrier.
      Variable Ap : object (X / U).

      Definition IsInitialMorphism_object (M : IsInitialMorphism Ap) : D
        := CommaCategory.b Ap.
      Definition IsInitialMorphism_morphism (M : IsInitialMorphism Ap)
      : morphism C X (U (IsInitialMorphism_object M))
        := CommaCategory.f Ap.
      Definition IsInitialMorphism_property_morphism (M : IsInitialMorphism Ap)
                 (Y : D) (f : morphism C X (U Y))
      : morphism D (IsInitialMorphism_object M) Y
        := CommaCategory.h
             (@center _ (M (CommaCategory.Build_object !X U tt Y f))).
      Definition IsInitialMorphism_property_morphism_property
                 (M : IsInitialMorphism Ap)
                 (Y : D) (f : morphism C X (U Y))
      : (U _1 (IsInitialMorphism_property_morphism M Y f))
          o IsInitialMorphism_morphism M = f
        := concat
             (CommaCategory.p
                (@center _ (M (CommaCategory.Build_object !X U tt Y f))))
             (right_identity _ _ _ _).
      Definition IsInitialMorphism_property_morphism_unique
                 (M : IsInitialMorphism Ap)
                 (Y : D) (f : morphism C X (U Y))
                 m'
                 (H : U _1 m' o IsInitialMorphism_morphism M = f)
      : IsInitialMorphism_property_morphism M Y f = m'
        := ap
             (@CommaCategory.h _ _ _ _ _ _ _)
             (@contr _
                     (M (CommaCategory.Build_object !X U tt Y f))
                     (CommaCategory.Build_morphism
                        Ap (CommaCategory.Build_object !X U tt Y f)
                        tt m' (H @ (right_identity _ _ _ _)^)%path)).
      Definition IsInitialMorphism_property
                 (M : IsInitialMorphism Ap)
                 (Y : D) (f : morphism C X (U Y))
      : Contr { m : morphism D (IsInitialMorphism_object M) Y
              | U _1 m o IsInitialMorphism_morphism M = f }
        := {| center := (IsInitialMorphism_property_morphism M Y f;
                         IsInitialMorphism_property_morphism_property M Y f);
              contr m' := path_sigma
                            _
                            (IsInitialMorphism_property_morphism M Y f;
                             IsInitialMorphism_property_morphism_property M Y f)
                            m'
                            (@IsInitialMorphism_property_morphism_unique M Y f m'.1 m'.2)
                            (center _) |}.
    End EliminationAbstractionBarrier.

    Global Arguments IsInitialMorphism_object : simpl never.
    Global Arguments IsInitialMorphism_morphism : simpl never.
    Global Arguments IsInitialMorphism_property : simpl never.
    Global Arguments IsInitialMorphism_property_morphism : simpl never.
    Global Arguments IsInitialMorphism_property_morphism_property : simpl never.
    Global Arguments IsInitialMorphism_property_morphism_unique : simpl never.
  End InitialMorphism.

  (** ** Terminal morphisms *)
  Section TerminalMorphism.
    (** *** Definition *)
    Variables C D : PreCategory.

    Variable U : Functor D C.
    Variable X : C.
    (** A terminal morphism from [U] to [X] is a terminal object in
       the comma category [(U ↓ X)] of morphisms from [U] to [X].  In
       other words, it consists of a pair [(A, φ)] where [A] is an
       object of [D] and [φ : U A -> X] is a morphism in [C], such
       that the following terminal property is satisfied:

       - Whenever [Y] is an object of [D] and [f : U Y -> X] is a
         morphism in [C], then there exists a unique morphism [g : Y
         -> A] such that the following diagram commutes:

<<
         Y      U Y
         .       . \
       g .   U g .   \  f
         .       .     \
         ↓       ↓       ↘
         A      U A -----> X
                      φ
>>
       *)
    Local Notation op_object Ap
      := (CommaCategory.Build_object
            (Functors.from_terminal C^op X) (U^op)
            (CommaCategory.b (Ap : object (U / X)))
            (CommaCategory.a (Ap : object (U / X)))
            (CommaCategory.f (Ap : object (U / X)))
          : object ((X : object C^op) / U^op)).

    Definition IsTerminalMorphism (Ap : object (U / X)) : Type
      := @IsInitialMorphism
           (C^op)
           _
           X
           (U^op)
           (op_object Ap).

    (** *** Introduction rule *)
    Section IntroductionAbstractionBarrier.
      Definition Build_IsTerminalMorphism
      : forall
          (*(Ap : Object (U ↓ X))*)
          (A : D)(* := CommaCategory.a Ap*)
          (p : morphism C (U A) X)(*:= CommaCategory.f Ap*)
          (Ap := CommaCategory.Build_object U !X A tt p)
          (UniversalProperty
           : forall (A' : D) (p' : morphism C (U A') X),
               Contr { m : morphism D A' A
                     | p o U _1 m = p' }),
          IsTerminalMorphism Ap
        := @Build_IsInitialMorphism
             (C^op)
             (D^op)
             X
             (U^op).

      Definition Build_IsTerminalMorphism_curried
      : forall
          (A : D)
          (p : morphism C (U A) X)
          (Ap := CommaCategory.Build_object U !X A tt p)
          (m : forall (A' : D) (p' : morphism C (U A') X),
                 morphism D A' A)
          (H : forall (A' : D) (p' : morphism C (U A') X),
                 p o U _1 (m A' p') = p')
          (H' : forall (A' : D) (p' : morphism C (U A') X) m',
                  p o U _1 m' = p'
                  -> m A' p' = m'),
          IsTerminalMorphism Ap
        := @Build_IsInitialMorphism_curried
             (C^op)
             (D^op)
             X
             (U^op).

      Definition Build_IsTerminalMorphism_uncurried
      : forall
          (univ : { A : D
                  | { p : morphism C (U A) X
                    | let Ap := CommaCategory.Build_object U !X A tt p in
                      forall (A' : D) (p' : morphism C (U A') X),
                        { m : morphism D A' A
                        | { H : p o U _1 m = p'
                          | forall m',
                              p o U _1 m' = p'
                              -> m = m' }}}}),
          IsTerminalMorphism (CommaCategory.Build_object U !X univ.1 tt univ.2.1)
        := @Build_IsInitialMorphism_uncurried
             (C^op)
             (D^op)
             X
             (U^op).
    End IntroductionAbstractionBarrier.

    (** *** Elimination rule *)
    Section EliminationAbstractionBarrier.
      Variable Ap : object (U / X).
      Variable M : IsTerminalMorphism Ap.

      Definition IsTerminalMorphism_object : D :=
        @IsInitialMorphism_object C^op D^op X U^op (op_object Ap) M.
      Definition IsTerminalMorphism_morphism
      : morphism C (U IsTerminalMorphism_object) X
        := @IsInitialMorphism_morphism C^op D^op X U^op (op_object Ap) M.
      Definition IsTerminalMorphism_property
      : forall (Y : D) (f : morphism C (U Y) X),
          Contr { m : morphism D Y IsTerminalMorphism_object
                | IsTerminalMorphism_morphism o U _1 m = f }
        := @IsInitialMorphism_property C^op D^op X U^op (op_object Ap) M.
      Definition IsTerminalMorphism_property_morphism
      : forall (Y : D) (f : morphism C (U Y) X),
          morphism D Y IsTerminalMorphism_object
        := @IsInitialMorphism_property_morphism
             C^op D^op X U^op (op_object Ap) M.
      Definition IsTerminalMorphism_property_morphism_property
      : forall (Y : D) (f : morphism C (U Y) X),
          IsTerminalMorphism_morphism
            o (U _1 (IsTerminalMorphism_property_morphism Y f))
          = f
        := @IsInitialMorphism_property_morphism_property
             C^op D^op X U^op (op_object Ap) M.
      Definition IsTerminalMorphism_property_morphism_unique
      : forall (Y : D) (f : morphism C (U Y) X)
               m'
               (H : IsTerminalMorphism_morphism o U _1 m' = f),
          IsTerminalMorphism_property_morphism Y f = m'
        := @IsInitialMorphism_property_morphism_unique
             C^op D^op X U^op (op_object Ap) M.
    End EliminationAbstractionBarrier.
  End TerminalMorphism.

  Section UniversalMorphism.
    (** The term universal morphism refers either to an initial
        morphism or a terminal morphism, and the term universal
        property refers either to an initial property or a terminal
        property.  In each definition, the existence of the morphism
        [g] intuitively expresses the fact that [(A, φ)] is ``general
        enough'', while the uniqueness of the morphism ensures that
        [(A, φ)] is ``not too general''.  *)
  End UniversalMorphism.
End UniversalMorphism.

Arguments Build_IsInitialMorphism [C D] X U A p UniversalProperty _.
Arguments Build_IsTerminalMorphism [C D] U X A p UniversalProperty _.


Require Import Verdi.Verdi.
Require Import Verdi.HandlerMonad.
Require Import Verdi.NameOverlay.
Require Import Verdi.LabeledNet.

Require Import TreeAux.
Require Import ZTreeAggregationDynamicLabeled.

Require Import InfSeqExt.infseq.

Require Import Relation_Definitions.
Require Import Relation_Operators.

Require Import Sumbool.
Require String.

Require Import mathcomp.ssreflect.ssreflect.
Require Import mathcomp.ssreflect.ssrbool.

Local Arguments update {_} {_} _ _ _ _ _ : simpl never.

Set Implicit Arguments.

Module ZTreeAggregationCorrect (Import NT : NameType)
 (NOT : NameOrderedType NT) (NSet : MSetInterface.S with Module E := NOT)
 (NOTC : NameOrderedTypeCompat NT) (NMap : FMapInterface.S with Module E := NOTC)
 (Import RNT : RootNameType NT)
 (Import ANT : AdjacentNameType NT)
 (Import TA : TAux NT NOT NSet NOTC NMap).

Module ZTA := ZTreeAggregation NT NOT NSet NOTC NMap RNT ANT TA.
Import ZTA.

Definition connected (ns : list name) :=
  forall n n', In n ns -> In n' ns ->
  n = n' \/ (clos_trans name adjacent_to) n n'.

Definition node_aggregate (state : name -> option data) (n : name) :=
match state n with
| None => 0
| Some d => d.(aggregate)
end.

Lemma churn_free_stabilization :
  forall s, event_step_star step_ordered_dynamic step_ordered_dynamic_init (hd s) ->
       connected (hd s).(evt_a).(odnwNodes) ->
       lb_step_execution lb_step_ordered_dynamic s ->
       weak_fairness lb_step_ordered_dynamic Tau s ->
       forall n, root n -> In n (hd s).(evt_a).(odnwNodes) ->
       eventually (always (now (fun e => length e.(evt_a).(odnwNodes) = node_aggregate e.(evt_a).(odnwState) n))) s.
Proof.
Admitted.

End ZTreeAggregationCorrect.


Require Import HoTT.
Require Import Syntax.ScopeSystem.
Require Import Auxiliary.Family.
Require Import Auxiliary.WellFounded.
Require Import Auxiliary.Coproduct.
Require Import Syntax.All.
Require Import Typing.Context.
Require Import Typing.Judgement.
Require Import Presented.AlgebraicExtension.
Require Import Typing.RawRule.
Require Import Typing.RawTypeTheory.
Require Import Presented.PresentedRawRule.
Require Import Presented.PresentedRawTypeTheory.
Require Import Presented.CongruenceRule.
Require Import Typing.Presuppositions.

(** In this file: definition of well-typedness of an algebraic extension, and a (well-presented) rule. *)

Section WellTypedRule.

  Context {σ : scope_system}.

  (* TODO: upstream to new file [TypedAlgebraicExtension]. *)
  Definition is_well_typed_algebraic_extension
      {Σ : signature σ} (T : raw_type_theory Σ)
      {a} (A : algebraic_extension Σ a)
    : Type.
  Proof.
    refine (forall r : A, _).
    exact (forall
     (i : presupposition_of_boundary (AlgebraicExtension.premise_boundary r)),
      RawTypeTheory.derivation
        (RawTypeTheory.fmap include_symbol T)
        (AlgebraicExtension.flatten (AlgebraicExtension.initial_segment A r))
        (presupposition_of_boundary _ i)).
  Defined.
  (* NOTE: when checking we want to add the earlier premises of [A] to [T], and typecheck [r] over that.  There are (at least) three ways to do this:
  (1) take earlier premises just as judgements, and allow them as hypotheses in the derivation;
  (2) take earlier premise as judgements, then add no-premise raw rules to [T], giving these judgements as axioms;
  (3) give the extension of [T] by the preceding part of [A] as a type theory, i.e. turn premises of [A] into genuine raw rules, with the variables of their contexts turned into term premises, and conclusion just the head of the given premise of [A].

  In any case we must first translate [T] up to the extended signature of [R].

  (1) would nicely fit into the monadic view of derivations.
  (3) would nicely factorise into “take initial segment of an alg ext”, and “extend TT by an alg ext”; also avoids the need to frequently use “cut” when invoking the earlier premises in the derivations

  We currently take option (1).
  *)

  (* TODO: consider naming.  Currently a bit out of step with our general convention of using e.g. [derivation] not [is_derivable]; but [derivation] would be misleading here, since this is the type of derivations showing that the rule is well-formed, not “derivations of the rule” in the usual sense of derivations showing it’s a derivable rule. *)
  Local Definition is_well_typed
      {Σ : signature σ} (T : raw_type_theory Σ)
      {a} {jf_concl}
      (R : rule Σ a jf_concl)
    : Type.
  Proof.
    refine (is_well_typed_algebraic_extension T (rule_premise R) * _).
    (* well-typedness of conclusion *)
    exact
      (forall (i : presupposition_of_boundary (PresentedRawRule.conclusion_boundary R)),
        RawTypeTheory.derivation
          (RawTypeTheory.fmap include_symbol T)
          (AlgebraicExtension.flatten (rule_premise R))
          (presupposition_of_boundary _ i)).
  Defined.

End WellTypedRule.

Section Functoriality.

  Context {σ : scope_system} `{Funext}.

  Definition fmap_is_well_typed_algebraic_extension
      {Σ Σ' : signature σ} (f : Signature.map Σ Σ')
      {a} {A : algebraic_extension Σ a}
      {T} (D : is_well_typed_algebraic_extension T A)
    : is_well_typed_algebraic_extension
        (RawTypeTheory.fmap f T) (AlgebraicExtension.fmap f A).
  Proof.
    unfold is_well_typed_algebraic_extension.
    intros p.
    change (AlgebraicExtension.premise_boundary p)
    with (Judgement.fmap_boundary
            (Metavariable.fmap1 f _)
            (@AlgebraicExtension.premise_boundary _ _ _ A p)).
    intros i.
    set (Di := D p (presupposition_fmap_boundary _ _ i)).
    (* [Di] is essentially what we want, modulo some translation. *)
    refine (transport _
      (Family.map_commutes (presupposition_fmap_boundary _ _) i) _).
    match goal with
    | [|- RawTypeTheory.derivation _ _ ?JJ ] => set (J := (JJ)) in *
    | _ => fail
    end.
    unfold Family.fmap, family_element in J.
    subst J.
    refine (RawTypeTheory.derivation_fmap_over_simple _ _ _ Di).
    - (* map in type theory *)
      (* TODO: refactor to be higher-level, eg using [RawTypeTheory.fmap_compose]? *)
      exists idmap.
      intros r; simpl.
      eapply concat. { apply inverse, RawRule.fmap_compose. }
      eapply concat. 2: { apply RawRule.fmap_compose. }
      apply ap10, ap.
      apply Metavariable.include_symbol_after_map.
    - (* map in hypotheses *)
      cbn. exists idmap; intros j.
      eapply concat. 2: { apply AlgebraicExtension.flatten_fmap. }
      apply AlgebraicExtension.flatten_initial_segment_fmap_applied.
  Defined.

  (** Well-typedness is _covariant_ in the signature *)
  Local Definition fmap_is_well_typed
      {Σ Σ' : signature σ} (f : Signature.map Σ Σ')
      {a} {jf_concl} {R : rule Σ a jf_concl}
      {T} (D : is_well_typed T R)
    : is_well_typed (RawTypeTheory.fmap f T) (PresentedRawRule.fmap f R).
  Proof.
    split.
    (* premises *)
    { apply fmap_is_well_typed_algebraic_extension, (fst D). }
    (* conclusion *)
    set (fR_concl := PresentedRawRule.conclusion_boundary (PresentedRawRule.fmap f R)).
    assert (e_concl : fR_concl = Judgement.fmap_boundary
                             (Metavariable.fmap1 f _)
                             (PresentedRawRule.conclusion_boundary R)).
      { apply PresentedRawRule.conclusion_boundary_fmap. }
    clearbody fR_concl. destruct e_concl^.
    intros i.
    set (Di := (snd D) (presupposition_fmap_boundary _ _ i)).
    (* [Di] is essentially what we want, modulo some translation. *)
    refine (transport _
      (Family.map_commutes (presupposition_fmap_boundary _ _) i) _).
    match goal with
    | [|- RawTypeTheory.derivation _ _ ?JJ ] => set (J := (JJ)) in *
    | _ => fail
    end.
    unfold Family.fmap, family_element in J.
    subst J.
    refine (RawTypeTheory.derivation_fmap_over_simple _ _ _ Di).
    - (* map in type theory *)
      (* TODO: refactor using [RawTypeTheory.fmap_compose]? *)
      exists idmap.
      intros r; simpl.
      eapply concat. { apply inverse, RawRule.fmap_compose. }
      eapply concat. 2: { apply RawRule.fmap_compose. }
      apply ap10, ap.
      apply Metavariable.include_symbol_after_map.
    - (* map in hypotheses *)
      cbn. exists idmap.
      apply AlgebraicExtension.flatten_fmap.
  Defined.

  (** Well-typedness is _contravariantly_ functorial in the ambient theory. *)
  (* TODO: consider naming! *)
  Definition fmap_is_well_typed_algebraic_extension_in_theory {Σ : signature σ}
      {T T' : raw_type_theory Σ} (f : RawTypeTheory.map T T')
      {a} {A : algebraic_extension Σ a}
    : is_well_typed_algebraic_extension T A
      -> is_well_typed_algebraic_extension T' A.
  Proof.
    intros A_WT r p.
    refine (RawTypeTheory.derivation_fmap1 _ (A_WT r p)).
    apply RawTypeTheory.map_fmap, f.
  Defined.

  (** Well-typedness is _contravariantly_ functorial in the ambient theory. *)
  (* TODO: consider naming! *)
  Definition fmap_is_well_typed_in_theory {Σ : signature σ}
      {T T' : raw_type_theory Σ} (f : RawTypeTheory.map T T')
      {a} {jf_concl} {R : rule Σ a jf_concl}
    : is_well_typed T R -> is_well_typed T' R.
  Proof.
    intros R_WT.
    split.
    { exact (fmap_is_well_typed_algebraic_extension_in_theory f (fst R_WT)). }
    intros p.
    refine (RawTypeTheory.derivation_fmap1 _ (snd R_WT p)).
    apply RawTypeTheory.map_fmap, f.
  Defined.

End Functoriality.

Section Flattening.

  Context {σ : scope_system} `{Funext}.

  Definition weakly_presuppositive_flatten
      {Σ : signature σ} {T : raw_type_theory Σ}
      {a} {jf_concl}
      {R : rule Σ a jf_concl}
      (T_WT : is_well_typed T R)
      (Sr : Judgement.is_object jf_concl ->
        {S : Σ.(family_index) &
        (symbol_arity S = a) * (symbol_class S = Judgement.class_of jf_concl)})
    : weakly_presuppositive_raw_rule T (PresentedRawRule.flatten R Sr).
  Proof.
    apply snd in T_WT.
    intros i.
    refine (Closure.graft' (T_WT i) _ _).
    - recursive_destruct jf_concl; apply idpath.
    - clear i. intros i.
      exact (Closure.hypothesis _ (_+_) (inl i)).
  Qed.
  (* NOTE: in fact, we should be able to extend this to showing
   that [flatten R] is _strongly_ presuppositive. *)

End Flattening.

Section Congruence_Rules.

  Context {σ : scope_system} `{Funext}.

  Definition congruence_rule_is_well_typed
      {Σ : signature σ} {T : raw_type_theory Σ}
      {a} {jf_concl} {R : rule Σ a jf_concl} (R_WT : is_well_typed T R)
      (R_is_ob : Judgement.is_object jf_concl)
      (S : Σ)
      (e_a : symbol_arity S = a)
      (e_cl : symbol_class S = class_of jf_concl)
    : is_well_typed T (congruence_rule R R_is_ob S e_a e_cl).
  Proof.
  Admitted. (* [congruence_rule_is_well_typed]: large, key stress test of functoriality framework for derivability *)

End Congruence_Rules.


Load LFindLoad.
From lfind Require Import LFind.
From QuickChick Require Import QuickChick.
From adtind Require Import goal33.

Derive Show for natural.

              Derive Arbitrary for natural.

              Instance Dec_Eq_natural : Dec_Eq natural.

              Proof. dec_eq. Qed.

Lemma conj13synthconj5 : forall  (lv0 : natural) (lv1 : natural) (lv2 : natural), (@eq natural (plus lv0 (mult (Succ lv1) lv2)) (plus (plus (mult lv1 lv2) lv2) lv0)).
Admitted.
QuickChick conj13synthconj5.


From Undecidability Require Import TM.TMEncoding TM.TMinL TM.Util.TM_facts.
From Undecidability.L Require Import Computability.MuRec.
Require Import Undecidability.Synthetic.Definitions.

Lemma TM_eval_halts Σ n (M : TM Σ n) q t q' t' :
  TM.eval M q t q' t' -> halt M q' = true.
Proof.
  induction 1; eauto.
Qed.

(* ** Reducing halting problem for TMs to halting problem for L *)
Theorem HaltMTM_to_HaltL :
  HaltMTM ⪯ HaltL.
Proof.
  eexists (fun '(existT2 (Sigma, n) M tp) =>
             (L.app mu (@ext _ _ _ (term_test (mk_mconfig (start M) tp))))).
  intros [ [Sigma n] M tp ]. cbn.
  unfold HaltL. setoid_rewrite eval_iff.
  split.
  - intros H.
    epose proof (Halt_red (mk_mconfig (start M) tp)) as [[v [H1 H2]] _].
    + destruct H as (? & ? & ?). eexists (mk_mconfig _ _).
      rewrite <- TM_eval_iff. split. 2:eassumption.
      eapply TM_eval_halts. eassumption.
    + exists v. split; eauto.
      eapply equiv_lambda. eassumption. eassumption.
  - intros [t [H1 H2]].
    epose proof (Halt_red (mk_mconfig (start M) tp)) as [_ [[q' t']]].
    + eexists. eauto.
    + exists q', t'. eapply TM_eval_iff. eapply H.
Qed.


Load LFindLoad.
From lfind Require Import LFind.
From QuickChick Require Import QuickChick.
From adtind Require Import goal33.

Derive Show for natural.

              Derive Arbitrary for natural.

              Instance Dec_Eq_natural : Dec_Eq natural.

              Proof. dec_eq. Qed.

Lemma conj12synthconj3 : forall  (lv0 : natural) (lv1 : natural), (@eq natural (plus lv0 (plus lv1 Zero)) (plus lv1 lv0)).
Admitted.
QuickChick conj12synthconj3.


(* Copyright (c) 2014, Robert Dockins *)

Require Import String.
Require Import List.

Require Import atoms.
Require Import permutations.

Require Import basics.
Require Import preord.
Require Import categories.
Require Import sets.
Require Import finsets.
Require Import esets.
Require Import effective.
Require Import directed.
Require Import plotkin.
Require Import joinable.
Require Import approx_rels.
Require Import cpo.
Require Import profinite.
Require Import discrete.

(**  We have arrow types and a single base type of booleans. *)
Inductive ty :=
  | ty_bool
  | ty_unit : ty
  | ty_prod : ty -> ty -> ty
  | ty_arrow : ty -> ty -> ty.

Delimit Scope ty_scope with ty.

Notation "x ⇒ y" := (ty_arrow x y) : ty_scope.
Notation "x × y" := (ty_prod x y) : ty_scope.
Local Open Scope ty.

Inductive term : ty -> ty -> Type :=

  | tbool : forall σ (b:bool),
                term σ ty_bool

  | tif : forall σ₁ σ₂,
                term σ₁ σ₂ ->
                term σ₁ σ₂ ->
                term σ₁ (ty_bool ⇒ σ₂)

  | tterminate : forall σ,
                term σ ty_unit

  | tident : forall σ,
                term σ σ

  | tcompose : forall σ₁ σ₂ σ₃,
                term σ₂ σ₃ ->
                term σ₁ σ₂ ->
                term σ₁ σ₃

  | tpair : forall σ₁ σ₂ σ₃,
                term σ₁ σ₂ ->
                term σ₁ σ₃ ->
                term σ₁ (σ₂ × σ₃)

  | tpi1 : forall σ₁ σ₂,
                term (σ₁ × σ₂) σ₁

  | tpi2 : forall σ₁ σ₂,
                term (σ₁ × σ₂) σ₂

  | tapp : forall σ₁ σ₂,
                term ((σ₁ ⇒ σ₂) × σ₁) σ₂

  | tcurry : forall σ₁ σ₂ σ₃,
                term (σ₁ × σ₂) σ₃ ->
                term σ₁ (σ₂ ⇒ σ₃).

(**  Types are interpreted as unpointed domains, using the discrete domain
     of booleans and the exponential in PLT.
  *)
Fixpoint tydom (τ:ty) : PLT :=
  match τ with
  | ty_bool => disc finbool
  | ty_unit => PLT.unit _
  | ty_prod τ₁ τ₂ => PLT.prod (tydom τ₁) (tydom τ₂)
  | ty_arrow τ₁ τ₂ => PLT.exp (tydom τ₁) (tydom τ₂)
  end.

(**  The denotation of terms is given by a simple fixpoint on term structure.
     The denotation of each combinator is a straightforward interpretation of the
     usual lambda term into the operations of a cartesian closed category.
  *)
Fixpoint denote (τ₁ τ₂:ty) (m:term τ₁ τ₂) : tydom τ₁ → tydom τ₂ :=
  match m in term τ₁ τ₂ return tydom τ₁ → tydom τ₂ with

  | tbool σ b => disc_elem b ∘ PLT.terminate false (tydom σ)

  | tterminate σ => PLT.terminate false (tydom σ)

  | tident σ => id(tydom σ)

  | tcompose σ₁ σ₂ σ₃ m₁ m₂ => denote σ₂ σ₃ m₁ ∘ denote σ₁ σ₂ m₂

  | tpair σ₁ σ₂ σ₃ m₁ m₂ => PLT.pair (denote σ₁ σ₂ m₁) (denote σ₁ σ₃ m₂)

  | tpi1 σ₁ σ₂ => PLT.pi1

  | tpi2 σ₁ σ₂ => PLT.pi2

  | tapp σ₁ σ₂ => PLT.app

  | tcurry σ₁ σ₂ σ₃ m => PLT.curry (denote (σ₁ × σ₂) σ₃ m)

  | tif σ₁ σ₂ th el => PLT.curry
        (disc_cases (fun b:bool => if b then denote σ₁ σ₂ th else denote σ₁ σ₂ el))
  end.

(**  The operational semantics is given in a big-step style, with the specification
     of redexes split out into a separate relation.
  *)

Inductive redex : forall σ₁ σ₂ σ₃, term σ₂ σ₃ -> term σ₁ σ₂ -> term σ₁ σ₃ -> Prop :=
  | redex_pi1 : forall σ₁ σ₂ σ₃ x y,
                  redex _ _ _ (tpi1 σ₁ σ₂) (tpair σ₃ σ₁ σ₂ x y) x

  | redex_pi2 : forall σ₁ σ₂ σ₃ x y,
                  redex _ _ _ (tpi2 σ₁ σ₂) (tpair σ₃ σ₁ σ₂ x y) y

  | redex_bool : forall σ₁ σ₂ x b,
                  redex σ₁ σ₂ ty_bool (tbool _ b) x (tbool _ b)

  | redex_terminate : forall σ₁ σ₂ (x:term σ₁ σ₂),
                  redex _ _ _ (tterminate _) x (tterminate _)

  | redex_ident : forall σ₁ σ₂ (x:term σ₁ σ₂),
                  redex _ _ _ (tident _) x x

  | redex_compose : forall σ₁ σ₂ σ₃ σ₄ x y (z:term σ₄ σ₁),
                  redex _ _ _ (tcompose σ₁ σ₂ σ₃ x y) z
                              (tcompose _ _ _ x (tcompose _ _ _ y z))

  | redex_beta : forall σ₁ σ₂ σ₃ (f:term (σ₁ × σ₂) σ₃) (g:term σ₁ σ₂),
                  redex _ _ _ (tapp _ _)
                              (tpair _ _ _ (tcurry _ _ _ f) g)
                              (tcompose _ _ _ f (tpair _ _ _ (tident _) g))

  | redex_iftrue : forall σ₁ σ₂ th el,
                  redex _ _ _ (tapp _ _)
                              (tpair _ _ _ (tif σ₁ σ₂ th el) (tbool _ true))
                              th

  | redex_iffalse : forall σ₁ σ₂ th el,
                  redex _ _ _ (tapp _ _)
                              (tpair _ _ _ (tif σ₁ σ₂ th el) (tbool _ false))
                              el

  | redex_curry : forall σ₁ σ₂ σ₃ σ₄ (f:term (σ₁×σ₂) σ₃) (g:term σ₄ σ₁),
                  redex _ _ _ (tcurry _ _ _ f) g
                              (tcurry _ _ _ (tcompose _ _ _ f
                                (tpair _ _ _ (tcompose _ _ _ g (tpi1 _ _)) (tpi2 _ _))))
  .

(*
  | redex_if_true : forall σ₁ σ₂ σ₃ th el (g:term σ₃ σ₁),
                  redex _ _ _ (tif σ₁ σ₂ th el)
                              (tpair _ _ _ g (tbool _ true))
                              (tcompose _ _ _ th g)

  | redex_if_false : forall σ₁ σ₂ σ₃ th el (g:term σ₃ σ₁),
                  redex _ _ _ (tif σ₁ σ₂ th el)
                              (tpair _ _ _ g (tbool _ false))
                              (tcompose _ _ _ el g).
*)
Inductive eval : forall τ₁ τ₂, term τ₁ τ₂ -> term τ₁ τ₂ -> Prop :=
  | ebool : forall σ b, eval σ ty_bool (tbool σ b) (tbool _ b)
  | epi1 : forall σ₁ σ₂, eval _ _ (tpi1 σ₁ σ₂) (tpi1 _ _)
  | epi2 : forall σ₁ σ₂, eval _ _ (tpi2 σ₁ σ₂) (tpi2 _ _)
  | eterminate : forall σ, eval _ _ (tterminate σ) (tterminate σ)
  | eident : forall σ, eval _ _ (tident σ) (tident _)
  | eif : forall σ₁ σ₂ th el, eval _ _ (tif σ₁ σ₂ th el) (tif _ _ th el)
  | ecurry : forall σ₁ σ₂ σ₃ m, eval _ _ (tcurry σ₁ σ₂ σ₃ m) (tcurry _ _ _ m)
  | eapp : forall σ₁ σ₂, eval _ _ (tapp σ₁ σ₂) (tapp σ₁ σ₂)
  | epair : forall σ₁ σ₂ σ₃ x y x' y',
         eval _ _ x x' ->
         eval _ _ y y' ->
         eval _ _ (tpair σ₁ σ₂ σ₃ x y) (tpair _ _ _ x' y')
  | ecompose1 : forall σ₁ σ₂ σ₃ x y x' y' r z,
         eval σ₂ σ₃ x x' ->
         eval σ₁ σ₂ y y' ->
         redex σ₁ σ₂ σ₃ x' y' r ->
         eval σ₁ σ₃ r z ->
         eval σ₁ σ₃ (tcompose σ₁ σ₂ σ₃ x y) z
  | ecompose2 : forall σ₁ σ₂ σ₃ x y x' y',
         eval σ₂ σ₃ x x' ->
         eval σ₁ σ₂ y y' ->
         (~exists r, redex σ₁ σ₂ σ₃ x' y' r) ->
         eval σ₁ σ₃ (tcompose σ₁ σ₂ σ₃ x y) (tcompose σ₁ σ₂ σ₃ x' y')
  .

(* FIXME: move to profinite.v *)
Lemma plt_terminate_univ : forall (A:PLT) (f:A → PLT.unit false),
  f ≈ PLT.terminate false A.
Proof.
  intros. split.
  apply PLT.terminate_le_univ.
  hnf; simpl; intros.
  destruct a.
  destruct u.
  destruct (PLT.hom_directed false _ _ f c nil); auto.
  hnf; auto. hnf; intros. apply nil_elem in H0. elim H0.
  destruct H0. apply erel_image_elem in H1. destruct x. auto.
Qed.

(*FIXME: move to discrete.v *)
Lemma disc_cases_elem'
     : forall (X : fintype) (A B C : PLT)
       (f : X -> A → B) (g: C → PLT.unit false) (x : X) (h : C → A),
       disc_cases f ∘ PLT.pair h (disc_elem x ∘ g) ≈ f x ∘ h.
Proof.
  split; intros a H. destruct a.
  apply compose_hom_rel in H.
  apply compose_hom_rel.
  destruct H as [q [??]].
  destruct q.
  apply (mk_disc_cases_elem X _ _ f (fintype.fintype_list X)) in H0.
  simpl in H.
  rewrite (pair_rel_elem _ _ _ _ _ _ c c1 c2) in H. destruct H.
  apply compose_elem in H1.  2: apply (PLT.hom_order _ _ _ g).
  simpl in *.
  destruct H1 as [q [??]].
  apply single_axiom in H2.
  exists c1. split; auto.
  destruct H2 as [[??][??]]. simpl in *.
  hnf in H3. subst c2. auto.
  apply fintype.fintype_complete.
  destruct a.
  apply compose_hom_rel in H.
  apply compose_hom_rel.
  destruct H as [q [??]].
  exists (q,x). split.
  apply pair_rel_elem. split; auto.
  apply compose_hom_rel.
  exists tt.
  split.
  destruct (PLT.hom_directed false _ _ g c nil); auto.
  hnf; auto. hnf; intros. apply nil_elem in H1. elim H1.
  destruct H1. apply erel_image_elem in H2. destruct x0. auto.
  simpl. apply single_axiom. auto.
  apply (mk_disc_cases_elem X _ _ f (fintype.fintype_list X)).
  apply fintype.fintype_complete.
  auto.
Qed.

(**  Every redex preserves the meaning of terms. *)
Lemma redex_soundness : forall σ₁ σ₂ σ₃ x y z,
  redex σ₁ σ₂ σ₃ x y z ->
  denote _ _ x ∘ denote _ _ y ≈ denote _ _ z.
Proof.
  intros. case H; simpl.

  intros. rewrite pair_commute1. auto.
  intros. rewrite pair_commute2. auto.
  intros. rewrite <- (cat_assoc _ _ _ _ _ (disc_elem b)).
  apply cat_respects; auto. apply plt_terminate_univ.
  intros. apply plt_terminate_univ.
  intros. apply cat_ident2.
  intros. symmetry. apply cat_assoc.
  intros. rewrite PLT.curry_apply2; auto.
  intros. rewrite PLT.curry_apply2; auto.
  rewrite disc_cases_elem'.
  apply cat_ident1.
  intros. rewrite PLT.curry_apply2; auto.
  rewrite disc_cases_elem'.
  apply cat_ident1.
  intros. rewrite PLT.curry_compose_commute.
  apply PLT.curry_eq.
  apply cat_respects; auto.
  unfold PLT.pair_map.
  apply PLT.pair_eq; auto.
  apply cat_ident2.
Qed.

(**  Evaluation preserves the denotation of terms. *)
Theorem soundness : forall τ₁ τ₂ (m z:term τ₁ τ₂),
  eval τ₁ τ₂ m z -> denote τ₁ τ₂ m ≈ denote τ₁ τ₂ z.
Proof.
  intros. induction H; simpl; auto.
  rewrite IHeval1.
  rewrite IHeval2.
  auto.
  rewrite IHeval1.
  rewrite IHeval2.
  rewrite <- IHeval3.
  apply redex_soundness. auto.
  rewrite IHeval1.
  rewrite IHeval2.
  auto.
Qed.

Notation synapp a b := (tcompose _ _ _ (tapp _ _) (tpair _ _ _ a b)).
Notation semapp a b := (PLT.app ∘ (PLT.pair a b)).

(**  Now we define the logical relation.  It is defined by induction
     on the structure of types, in a standard way.
  *)
Fixpoint LR Γ (τ₂:ty) : term Γ τ₂ -> (tydom Γ → tydom τ₂) -> Prop :=
  match τ₂ as τ' return term Γ τ' -> (tydom Γ → tydom τ') -> Prop
  with

  | ty_unit => fun m h => True

  | ty_bool => fun m h => denote _ _ m ≈ h

  | ty_prod σ₁ σ₂ => fun m h =>
            LR Γ σ₁ (tcompose _ _ _ (tpi1 _ _) m) (PLT.pi1 ∘ h) /\
            LR Γ σ₂ (tcompose _ _ _ (tpi2 _ _) m) (PLT.pi2 ∘ h)

  | ty_arrow σ₁ σ₂ => fun m h =>
        forall n h',
          LR Γ σ₁ n h' -> eval _ σ₁ n n ->
          exists z,
            eval Γ σ₂ (synapp m n) z /\
            LR Γ σ₂ z (semapp h h')
  end.

(**  Syntactic types have decicable equality, which
     implies injectivity for dependent pairs with
     (syntactic) types as the type being depended upon.
  *)
Lemma inj_pair2_ty : forall (F:ty -> Type) τ x y,
  existT F τ x = existT F τ y -> x = y.
Proof.
  intros.
  apply Eqdep_dec.inj_pair2_eq_dec in H. auto.
  decide equality.
Qed.

Ltac inj_ty :=
  repeat match goal with
           [ H : existT _ _ _ = existT _ _ _ |- _ ] =>
             apply inj_pair2_ty in H
           end.

Ltac inv H :=
  inversion H; subst; inj_ty; repeat subst.

(**  Now we move on to the more difficult adequacy proof.
     For this we will first need a variety of technical results
     about the operational semantics.
  *)

Lemma eval_value τ₁ τ₂ x y :
  eval τ₁ τ₂ x y -> eval τ₁ τ₂ y y.
Proof.
  intro H. induction H; try (constructor; fail); auto.
  apply epair; auto.
  apply ecompose2; auto.
Qed.

Lemma redex_eq τ₁ τ₂ τ₃ x y z1 z2 :
  redex τ₁ τ₂ τ₃ x y z1 ->
  redex τ₁ τ₂ τ₃ x y z2 ->
  z1 = z2.
Proof.
  intros; inv H; inv H0; auto.
  inv H; auto.
Qed.

Lemma eval_eq τ₁ τ₂ x y1 y2 :
  eval τ₁ τ₂ x y1 -> eval τ₁ τ₂ x y2 -> y1 = y2.
Proof.
  intro H. revert y2.
  induction H.

  intros. inv H. auto.
  intros. inv H. auto.
  intros. inv H. auto.
  intros. inv H. auto.
  intros. inv H. auto.
  intros. inv H. auto.
  intros. inv H. auto.

  intros. inversion H.
  subst. inj_ty. auto.

  intros. inv H1.
  apply IHeval1 in H6.
  apply IHeval2 in H9.
  subst y'0 x'0.
  auto.

  intros. inv H3.
  apply IHeval1 in H10.
  apply IHeval2 in H11.
  subst y'0 x'0.
  assert (r = r0).
  eapply redex_eq; eauto.
  subst r0.
  apply IHeval3; auto.

  apply IHeval1 in H10.
  apply IHeval2 in H11.
  subst y'0 x'0.
  elim H12; eauto.

  intros. inv H2.
  apply IHeval1 in H9.
  apply IHeval2 in H10.
  subst y'0 x'0.
  elim H1. eauto.
  f_equal; auto.
Qed.

Lemma eval_trans τ₁ τ₂ x y z :
  eval τ₁ τ₂ x y -> eval τ₁ τ₂ y z -> eval τ₁ τ₂ x z.
Proof.
  intros.
  replace z with y; auto.
  eapply eval_eq with y; auto.
  eapply eval_value; eauto.
Qed.

Lemma eval_app_congruence σ₁ σ₂ σ₃ : forall x x' y y' z,
  (forall q, eval _ _ x q -> eval _ _ x' q) ->
  (forall q, eval _ _ y q -> eval _ _ y' q) ->
  eval _ _ (tcompose σ₁ σ₂ σ₃ x y) z ->
  eval _ _ (tcompose σ₁ σ₂ σ₃ x' y') z.
Proof.
  intros.
  inv H1.
  apply H in H8.
  apply H0 in H9.
  eapply ecompose1; eauto.
  apply ecompose2; eauto.
Qed.

Lemma eval_redex_walk : forall t1 t2 t3 x y z q,
  redex t1 t2 t3 x y z ->
  eval _ _ x x ->
  eval _ _ y y ->
  eval _ _ (tcompose _ _ _ x y) q ->
  eval _ _ z q.
Proof.
  intros.
  inv H2.
  assert (x = x'). eapply eval_eq; eauto.
  assert (y = y'). eapply eval_eq; eauto.
  subst x' y'.
  assert (r = z). eapply redex_eq; eauto.
  subst r. auto.
  assert (x = x'). eapply eval_eq; eauto.
  assert (y = y'). eapply eval_eq; eauto.
  subst x' y'.
  elim H11. eauto.
Qed.

Lemma LR_equiv τ : forall Γ m h h',
  h ≈ h' -> LR Γ τ m h -> LR Γ τ m h'.
Proof.
  induction τ; simpl. intros.
  intuition. rewrite <- H; auto.
  simpl; intuition.
  simpl; intuition.
  revert H1. apply IHτ1.
  apply cat_respects; auto.
  revert H2. apply IHτ2.
  apply cat_respects; auto.

  simpl; intros; auto.
  destruct (H0 n h'0 H1 H2) as [z [??]].
  exists z; split; auto.
  revert H4. apply IHτ2.
  apply cat_respects; auto.
  apply PLT.pair_eq; auto.
Qed.

(**  Now we need a host of auxilary definitions to state
     the main lemmas regarding the logical relation.  These
     definitions allow us to apply an arbitrary number of
     arguments to a syntactic term and to the denotation of terms.
  *)

Fixpoint lrtys (z:ty) (ts:list ty) :=
  match ts with
  | nil => z
  | t::ts' => t ⇒ (lrtys z ts')
  end.

Fixpoint lrsyn Γ (ts:list ty) : Type :=
  match ts with
  | nil => unit
  | t::ts' => prod (lrsyn Γ ts') (term Γ t)
  end.

Fixpoint lrsem Γ (ts:list ty) : Type :=
  match ts with
  | nil => unit
  | t::ts' => prod (lrsem Γ ts') (tydom Γ → tydom t)
  end.

Fixpoint lrhyps Γ (ls:list ty) : lrsyn Γ ls -> lrsem Γ ls -> Prop :=
  match ls as ls' return lrsyn Γ ls' -> lrsem Γ ls' -> Prop with
  | nil => fun _ _ => True
  | t::ts => fun xs ys =>
      eval _ _ (snd xs) (snd xs) /\
      LR Γ _ (snd xs) (snd ys) /\
      lrhyps Γ ts (fst xs) (fst ys)
  end.

Fixpoint lrapp Γ σ (ls:list ty) : term Γ (lrtys σ ls) -> lrsyn Γ ls -> term Γ σ :=
  match ls as ls' return
    term Γ (lrtys σ ls') -> lrsyn Γ ls' -> term Γ σ
  with
  | nil => fun m _ => m
  | t::ts => fun m xs => lrapp Γ σ ts (synapp m (snd xs)) (fst xs)
  end.

Fixpoint lrsemapp Γ σ (ls:list ty) :
  (tydom Γ → tydom (lrtys σ ls)) -> lrsem Γ ls -> (tydom Γ → tydom σ) :=
  match ls as ls' return
    (tydom Γ → tydom (lrtys σ ls')) -> lrsem Γ ls' -> (tydom Γ → tydom σ)
  with
  | nil => fun h _ => h
  | t::ts => fun h ys => lrsemapp Γ σ ts (semapp h (snd ys)) (fst ys)
  end.

Lemma eval_lrapp_congruence ls : forall Γ τ xs m m' z,
  (forall q, eval _ _ m q -> eval _ _ m' q) ->
  eval _ _ (lrapp Γ τ ls m xs) z ->
  eval _ _ (lrapp Γ τ ls m' xs) z.
Proof.
  induction ls; simpl; intros.
  apply H. auto.

  revert H0. apply IHls.
  intros.
  inv H0; fold lrtys in *.
  eapply ecompose1; eauto.
  inv H8. apply epair; auto.
  eapply ecompose2; eauto.
  inv H8. apply epair; auto.
Qed.

Lemma lrsemapp_equiv ls : forall Γ τ ys h h',
  h ≈ h' -> lrsemapp Γ τ ls h ys ≈ lrsemapp Γ τ ls h' ys.
Proof.
  induction ls; simpl; intros; auto.
  apply IHls.
  apply cat_respects; auto.
  apply PLT.pair_eq; auto.
Qed.

(**  The main logical relations lemma.  In order to make the induction
     function properly, we have to play some games with equality coercions :-(

     We work through the issues that arise via liberal application of the
     Eqdep_dec.UIP_dec lemma, which applies to types with decidable equality.
  *)
Lemma LRok : forall Γ σ (n:term Γ σ) (ls:list ty) τ (m:term Γ (lrtys τ ls)) xs ys
  (Hσ : σ = lrtys τ ls),
  eq_rect σ (fun a => term Γ a) n (lrtys τ ls) Hσ = m ->
  lrhyps Γ ls xs ys ->
  exists z,
    eval Γ τ (lrapp Γ τ ls m xs) z /\
    LR Γ τ z (lrsemapp Γ τ ls (denote _ _ m) ys).
Proof.
  induction n; intros.

admit.
admit.
admit.
admit.

  subst. simpl in *.

(**  Now we define contextual equivalance.  Contexts here are
     given in "inside-out" form, which makes the induction in the
     adequacy proof significantly easier.
  *)

Inductive context τ : ty -> Type :=
  | cxt_top : context τ τ
  | cxt_appl : forall σ₁ σ₂,
                    term σ₁ ->
                    context τ σ₂ ->
                    context τ (σ₁ ⇒ σ₂)
  | cxt_appr : forall σ₁ σ₂,
                    term (σ₁ ⇒ σ₂) ->
                    context τ σ₂ ->
                    context τ σ₁.

Fixpoint plug τ σ (C:context τ σ) : term σ -> term τ :=
  match C in context _ σ return term σ -> term τ with
  | cxt_top => fun x => x
  | cxt_appl σ₁ σ₂ t C' => fun x => plug τ _ C' (tapp x t)
  | cxt_appr σ₁ σ₂ t C' => fun x => plug τ _ C' (tapp t x)
  end.

Definition cxt_eq τ σ (m n:term σ):=
  forall (C:context τ σ) (z:term τ),
    eval τ (plug τ σ C m) z <-> eval τ (plug τ σ C n) z.

(**  Adequacy means that terms with equivalant denotations
     are contextually equivalant in any boolean context.
  *)
Theorem adequacy : forall τ (m n:term τ),
  denote τ m ≈ denote τ n -> cxt_eq ty_bool τ m n.
Proof.
  intros. intro.
  revert n m H.
  induction C.

  simpl; intros.
  destruct (LRok _ m nil _ m tt tt (Logic.refl_equal _) (Logic.refl_equal _) I)
    as [zm [??]]. simpl in *.
  destruct (LRok _ n nil _ n tt tt (Logic.refl_equal _) (Logic.refl_equal _) I)
    as [zn [??]]. simpl in *.
  destruct H1 as [bm [??]].
  destruct H3 as [bn [??]].
  subst zm zn.
  rewrite H in H4.
  rewrite H4 in H5.
  assert (bm = bn).
  apply disc_elem_inj in H5. auto.
  subst bn.
  split; intro.
  assert (z = (tbool bm)).
  eapply eval_eq; eauto.
  subst z. auto.
  assert (z = (tbool bm)).
  eapply eval_eq; eauto.
  subst z. auto.

  simpl. intros.
  apply IHC. simpl.
  apply cat_respects; auto.
  apply PLT.pair_eq; auto.

  simpl; intros.
  apply IHC. simpl.
  apply cat_respects; auto.
  apply PLT.pair_eq; auto.
Qed.

(**  As a corollary of the logical relations lemma, we learn that
     the calculus is strongly normalizing.
  *)
Corollary normalizing : forall τ (m:term τ), exists z, eval τ m z.
Proof.
  intros.
  generalize (LRok τ m nil τ m tt tt (Logic.refl_equal _) (Logic.refl_equal _) I).
  simpl. intros [z [??]]. exists z; auto.
Qed.

(** These should print "Closed under the global context", meaning these
    theorems hold without the use of any axioms.
  *)
Print Assumptions adequacy.
Print Assumptions normalizing.


Require Import Coq.ZArith.BinInt Coq.ZArith.ZArith Coq.ZArith.Zdiv Coq.micromega.Lia.
Require Import Crypto.Util.LetIn.
Require Import Crypto.Util.Prod.
Require Import Crypto.Util.ZUtil.
Require Import Crypto.Arithmetic.ModularArithmeticTheorems Crypto.Spec.ModularArithmetic.
Require Import Crypto.Util.Sigma.
Local Open Scope Z_scope.

Section WordByWordMontgomery.
  Context
    {T : Type}
    {eval : T -> Z}
    {numlimbs : T -> nat}
    {divmod : T -> T * Z} (* returns lowest limb and all-but-lowest-limb *)
    {r : positive}
    {r_big : r > 1}
    {eval_div : forall v, eval (fst (divmod v)) = eval v / r}
    {eval_mod : forall v, snd (divmod v) = eval v mod r}
    {scmul : Z -> T -> T} (* uses double-output multiply *)
    {eval_scmul: forall a v, eval (scmul a v) = a * eval v}
    {R : positive}.
  Local Notation bn := (r * R) (only parsing).
  Context
    {add : T -> T -> T * Z} (* produces carry *)
    {eval_fst_add : forall a b, eval (fst (add a b)) = (eval a + eval b) mod bn}
    {eval_snd_add : forall a b, snd (add a b) = (eval a + eval b) / bn}
    {join : T * Z -> T} (* adds limb to high end *)
    {eval_join : forall p, eval (join p) = eval (fst p) + (bn / r) * (snd p)}
    {eval_nonneg : forall v, 0 <= eval v}
    (N : T) (Npos : positive) (Npos_correct: eval N = Z.pos Npos)
    (B : T)
    (B_small : eval B < R).

  Create HintDb push_eval discriminated.
  Hint Rewrite
       eval_div
       eval_mod
       eval_fst_add
       eval_snd_add
       eval_scmul
       eval_join
    : push_eval.

  (* Recurse for a as many iterations as A has limbs, varying A := A, S := 0, r, bounds *)
  Section Iteration.
    Context (A S : T)
            (S_small : eval S / R <= 1).
    Context (k : Z) (k_correct : k * eval N mod r = -1).
    (* Given A, B < R, we want to compute A * B / R mod N. R = bound 0 * ... * bound (n-1) *)
    Let A_a := dlet p := divmod A in p. Let A' := fst A_a. Let a := snd A_a.
    Let S1 := fst (add S (scmul a B)).
    Let s := snd (divmod S1).
    Let q := s * k mod r.
    Let cS2 := add S1 (scmul q N).
    Let c := snd cS2.
    Let S3 := fst (divmod (fst cS2)).

    Local Coercion eval : T >-> Z.

    Lemma S_aB_over_R_bound : (eval S + a * eval B) / R <= 1 + ((r - 1)^2 / r + 1 + 1).
    Proof.
      assert (Hmod0 : forall v r, 0 < r -> v mod r < r) by (intros; apply Z_mod_lt; lia).
      assert (Hmod : forall v r, 0 < r -> v mod r <= r - 1) by (intros v m; specialize (Hmod0 v m); omega).
      rewrite (Z_div_mod_eq (eval S) R), <- Z.add_assoc, Z.div_add_l' by lia.
      apply Z.add_le_mono; auto.
      transitivity ((R-1 + (r-1)*(R-1)) / R).
      { Z.peel_le; apply Z.add_le_mono; auto with lia.
        unfold a, A_a, Let_In.
        autorewrite with push_eval.
        apply Z.mul_le_mono_nonneg; try solve [ auto with lia | lia | apply Z_mod_lt; lia ]. }
      { admit. (*
      Unset Printing Coercions.
      Require Import Algebra.Ring.
      ring_simplify_subterms.

      transitivity (r + (-r) / R).
      admit.
      transitivity (r - 2 + 1/r + 2); [ | admit ].
      transitivity r.
      admit.

      am
      SearchAbout ((?x * _ + _) / ?x).

      SearchAbout ((?x * _ + _) / ?x).
      SearchAbout (?x / ?y) (?x mod ?y).
      SearchAbout ((_ + _) / _). *) }
    Admitted.

    Lemma S_aB_range : 0 <= eval S + a * eval B < bn.
    Proof.
      pose proof S_aB_over_R_bound.
      assert (eval S + a * eval B <= (R - 1) + R + ((Z.pos r - 1) ^ 2 / Z.pos r + 1 + 1) * R) by admit.
      (*Z.div_mod_to_quot_rem.
      nia.
      assert (eval A mod Z.pos r < r) by (apply Z_mod_lt; lia).
      assert (Hmod : eval A mod Z.pos r <= r - 1) by omega.
      repeat match goal with H := _ |- _ => progress unfold H end.
      unfold Let_In.
      rewrite eval_mod.
      split; [ solve [ auto with zarith ] | ].
      eapply (@Z.le_lt_trans _ (2 * R - 1 + (r - 1) * (R - 1))).
      Focus 2.
      rewrite Z.mul_sub_distr_l, !Z.mul_sub_distr_r, !Z.add_sub_assoc.
      Require Import Crypto.Algebra.Ring.
      ring_simplify_subterms.
      Unset Printing Coercions.
      autorewrite with zsimplify.
      change
      repeat (apply Z.add_le_mono || apply Z.mul_le_mono_nonneg); try lia.
      Z.peel_le.
      rewrite S_small.

      Focus 2.
      Unset Printing Coercions.

      rewrite Z.mul_sub_distr_l, !Z.mul_sub_distr_r, !Z.add_sub_assoc.
      autorewrite with zsimplify.
      omega.
      autorewrite with *)
    Admitted.

    Lemma S1_eq : eval S1 = S + a*B.
    Proof.
      cbv [S1 a A'].
      repeat autorewrite with push_eval.
      rewrite (Z.mod_small _ bn). (* by (apply aB_range). *)
      reflexivity.
      Unset Printing Coercions.
    Admitted.

    Lemma cS2_mod_N : (eval (fst cS2) + bn * snd cS2) mod N = (S + a*B) mod N.
    Proof.
      assert (bn_large : bn >= r) by (unfold bn; nia).
      cbv [cS2 q s]; autorewrite with push_eval zsimplify. rewrite S1_eq. reflexivity.
    Qed.

    Lemma cS2_mod_r : fst cS2 mod r = 0.
      cbv [cS2 q s]; autorewrite with push_eval.
      assert (r > 0) by lia.
      assert (Hr : (-(1 mod r)) mod r = r - 1 /\ (-(1)) mod r = r - 1).
      { destruct (Z.eq_dec r 1) as [H'|H'].
        { rewrite H'; split; reflexivity. }
        { rewrite !Z_mod_nz_opp_full; rewrite ?Z.mod_mod; Z.rewrite_mod_small; [ split; reflexivity | omega.. ]. } }
      autorewrite with pull_Zmod.
      rewrite (Z.mod_small _ bn) by admit.
      replace 0 with (0 mod r) by apply Zmod_0_l.
      eapply F.eq_of_Z_iff.
      repeat rewrite ?F.of_Z_add, ?F.of_Z_mul, <-?F.of_Z_mod.
      rewrite <-Algebra.Hierarchy.associative.
      replace ((F.of_Z r k * F.of_Z r (eval N))%F) with (F.opp (m:=r) F.one).
      { cbv [F.of_Z F.add]; simpl.
        apply path_sig_hprop; [ intro; exact HProp.allpath_hprop | ].
        simpl.
        rewrite (proj1 Hr), Z.mul_sub_distr_l.
        push_Zmod; pull_Zmod.
        autorewrite with zsimplify; reflexivity. }
      { rewrite <- F.of_Z_mul.
        rewrite F.of_Z_mod.
        rewrite k_correct.
        cbv [F.of_Z F.add F.opp F.one]; simpl.
        change (-(1)) with (-1) in *.
        apply path_sig_hprop; [ intro; exact HProp.allpath_hprop | ]; simpl.
        rewrite (proj1 Hr), (proj2 Hr); reflexivity. }
      Grab Existential Variables.

    Admitted.

    Lemma cS3_mod_N ri (ri_correct : r*ri mod N = 1 mod N)
      : join (S3, c) mod N = (S + a*B)*ri mod N.
    Proof.
      assert (r_div_bn : (r | bn)) by apply Z.divide_factor_l.
      cbv [S3 c]; autorewrite with push_eval cancel_pair.
      replace (eval (fst cS2) / Z.pos r + (bn / r) * snd cS2)
        with ((eval (fst cS2) + bn * snd cS2) / Z.pos r)
        by (rewrite Z.div_add_exact, !(Z.mul_comm bn), Z.divide_div_mul_exact
             by (assumption || apply cS2_mod_r || lia); nia).
      pose proof fun a => Z.div_to_inv_modulo N a r ri eq_refl ri_correct as HH;
                            cbv [Z.equiv_modulo] in HH; rewrite HH; clear HH.
      etransitivity; [rewrite (fun a => Z.mul_mod_l a ri N)|
                      rewrite (fun a => Z.mul_mod_l a ri N); reflexivity].
      rewrite <-cS2_mod_N; repeat (f_equal; []); autorewrite with push_eval.
      autorewrite with push_Zmod;
        replace (bn mod r) with 0
        by (symmetry; apply Z.mod_divide; try assumption; try lia);
        rewrite cS2_mod_r;
        autorewrite with zsimplify.
      reflexivity.
    Qed.


Require Import Coq.Lists.List.
Require Import Coq.Strings.String.
Import ListNotations.

Open Scope type_scope.
Open Scope string_scope.

(* First we define lexes, which are building blocks for lexemes. These
are not lexemes themselves because we require additional structure to
develop a notion of derivational relationships. *)

Inductive lex : Set :=
| OS : lex (* father *)
| NESHNABÉ : lex (* person *)
| BIWABEK : lex (* metal *)
| MOWECH : lex (* feces *)
| BAGJÉʔGEN : lex. (* pool cue *)

(* Here we define the only derivational relationship that we are
concerned with for this fragment, IJ, which relates lexes to a lexeme
that is related by the function c to the class appropriate to
inalienably possessed nouns. *)

Inductive rel : Set :=
| IJ : rel. (* fellow *)

(* Lexemes are either simple, in the case of root or have a
relationship to a class of likewise derived lexemes in the case of
cmplx. *)

Inductive lexeme : Type :=
| root (l : lex)
| cmplx (r : rel) (l : lex).

(* The type mm is for atoms used in m-categories or mcats, which are
lists of mms. Here this is somewhat redundant because our fragment is
so simple. *)

Inductive mm : Set :=
| free : mm
| freem : mm
| bound : mm
| nposs : mm.

(* I define fclasses that are not used in this fragment. These are
related to lexemes by the function c. *)

Inductive fclass : Set :=
| long : fclass
| short : fclass
| nosuffix : fclass
| msuffix : fclass
| LN : fclass
| LM : fclass
| SN : fclass
| SM : fclass
| LNM : fclass
| LSN : fclass
| LSM : fclass
| SNM : fclass
| LSNM : fclass
| none : fclass. (* This will exist outside the order. *)

(* As stated above, mcat is a list of mms. An mform is a string. *)

Definition mcat := list mm.
Definition mform := string.

(* An mtrip is the data structure used to define Fentrys *)

Definition mtrip := mcat * mform * lexeme.

(* Note that the check below demonstrates why simply saying that we're
working with mtrips is not good enough. Non-grammatical triples
check. We need to be able to specify which are legal triples. *)

Check ([ SNM ] , "walk" , root OS).

(* To solve this I define a predicate Fentry, the use of which will be
seen below. Essentially, only grammatical mtrips will be Fentrys. *)

Axiom Fentry : mtrip -> Prop.

(* lem is the order over mcats. The axioms below define it as an
order. *)

Axiom lem : mcat -> mcat -> Prop.
Axiom lem_refl : forall x : mcat, lem x x.
Axiom lem_antisym : forall x y : mcat, lem x y -> lem y x -> x = y.
Axiom lem_trans : forall x y z : mcat, lem x y -> lem y z -> lem x z.

(* The order is not natural. We define, explicitly, the relation. *)

Axiom free_lem_bound : lem [ free ] [ bound ].
Axiom free_lem_freem : lem [ free ] [ freem ].

(* lef is the order over fclasses. The axioms below define it as an
order. *)

Axiom lef : fclass -> fclass -> Prop.
Axiom lef_refl : forall x : fclass, lef x x.
Axiom lef_antisym : forall x y : fclass, lef x y -> lef y x -> x = y.
Axiom lef_trans : forall x y z : fclass, lef x y -> lef y z -> lef x z.

(* The order is not natural. We define, explicitly, the relation. *)

Axiom ln_lef_long : lef LN long.
Axiom lm_lef_long : lef LM long.
Axiom ln_lef_nosuffix : lef LN nosuffix.
Axiom sn_lef_nosuffix : lef SN nosuffix.
Axiom lm_lef_msuffix : lef LM msuffix.
Axiom sm_lef_msuffix : lef SM msuffix.
Axiom sn_lef_short : lef SN short.
Axiom sm_lef_short : lef SM short.
Axiom lnm_lef_ln : lef LNM LN.
Axiom lsn_lef_ln : lef LSN LN.
Axiom lnm_lef_lm : lef LNM LM.
Axiom lsm_lef_lm : lef LSM LM.
Axiom lsn_lef_sn : lef LSN SN.
Axiom snm_lef_sn : lef SNM SN.
Axiom lsm_lef_sm : lef LSM SM.
Axiom snm_lef_sm : lef SNM SM.
Axiom lsnm_lef_lnm : lef LSNM LNM.
Axiom lsnm_lef_lsm : lef LSNM LSM.

(* Below we define c which relates lexemes to their fclass. We assume
that only cmplx IJ NESHNABÉ will have an fclass that allows for the
derivation from root NESHNABÉ as this is the only attested lexeme for
that derivation among the lexemes defined. *)

Definition c (l:lexeme) : fclass :=
  match l with
  | root OS => SN
  | root NESHNABÉ => LM
  | root BIWABEK => LSM (* We'd predict LSNM but these are attested. *)
  | root BAGJÉʔGEN => SNM (* The same as above. *)
  | root MOWECH => LSNM (* There are missing forms here too. *)
  | cmplx IJ lx => match lx with
                   | NESHNABÉ => SN
                   | _ => none
                   end
  end.

(* Below I define the form building functions. These ignore the
intricacies of providing correct surface forms as a simplification and
due to a lack of the specification of a phonological interface. *)

Definition suffix_m (s:mform) : mform :=
  s ++ "m".

Definition prefix_n (s:mform) : mform :=
  "n" ++ s.

Definition prefix_ned (s:mform) : mform :=
  "ned" ++ s.

Definition prefix_ij (s:mform) : mform :=
  "ij" ++ s.

(* These are definitions of basic stems for the lexemes above. Note
that I define the mtrips first and then specify that they are
Fentrys. *)

Definition mowech_free := ([ free ], "mowech", root MOWECH) : mtrip.

Definition os_bound := ([ bound ], "os", root OS) : mtrip.

Axiom f_mowech_free : Fentry mowech_free.

(* f_m is the rule for specifying that... *)

Axiom f_m : forall (mt : mtrip), (Fentry mt) -> (lem (fst (fst mt)) [ freem ]) -> (lef (c (snd mt)) msuffix) -> Fentry ([ bound ], suffix_m (snd (fst mt)), (snd mt)).

Axiom f_n : forall (mt : mtrip), (Fentry mt) -> (lem (fst (fst mt)) [ bound ]) -> (lef (c (snd mt)) short) -> Fentry ([ nposs ], prefix_n (snd (fst mt)), (snd mt)).

Axiom f_ned : forall (mt : mtrip), (Fentry mt) -> (lem (fst (fst mt)) [ bound ]) -> (lef (c (snd mt)) long) -> Fentry ([ nposs ], prefix_ned (snd (fst mt)), (snd mt)).

Definition glex (l:lexeme) :=
  match l with
  | root lx => lx
  | cmplx _ lx => lx
  end.

Axiom f_ij : forall (mt : mtrip), (Fentry mt) -> (lem (fst (fst mt)) [ freem ]) -> (lef (c (snd mt)) msuffix) -> Fentry ([ bound ], prefix_ij (snd (fst mt)), cmplx IJ (glex (snd mt))).

Lemma walk_base_is_fentry:
  Fentry mowech_free.
Proof.
  apply f_mowech_free.
Qed.

Lemma mowech_free_is_free:
  lem (fst (fst mowech_free)) [ free ].
Proof.
  simpl.
  apply lem_refl.
Qed.

Lemma mowech_free_is_LSNM:
  lef (c (snd mowech_free)) LSNM.
Proof.
  simpl.
  apply lef_refl.
Qed.

Lemma LM_is_msuffix:
  lef LM msuffix.
Proof.
  apply lm_lef_msuffix.
Qed.

Lemma LNM_is_msuffix:
  lef LNM msuffix.
Proof.
  assert (H1 : lef LNM LM).
  { apply lnm_lef_lm. }
  assert (H2 : lef LM msuffix).
  { apply lm_lef_msuffix. }
  apply (lef_trans LNM LM msuffix H1 H2).
Qed.

Lemma mowech_free_is_msuffix:
  lef (c (snd mowech_free)) msuffix.
Proof.
  simpl.
  assert (H1 : lef LSNM LNM).
  { apply lsnm_lef_lnm. }
  assert (H2 : lef LNM msuffix).
  { exact LNM_is_msuffix. }
  apply (lef_trans LSNM LNM msuffix H1 H2).
Qed.

Example test_f_m:
  Fentry ([ bound ], "mowechm", root MOWECH).
Proof.
  assert (H1 : lem [ free ] [ freem ]).
  { apply free_lem_freem. }
  assert (H2 : lef LSNM msuffix).
  { exact mowech_free_is_msuffix. }
  apply (f_m mowech_free).
  exact f_mowech_free.
  simpl.
  assumption.
  simpl.
  assumption.
Qed.


(* This program is free software; you can redistribute it and/or      *)
(* modify it under the terms of the GNU Lesser General Public License *)
(* as published by the Free Software Foundation; either version 2.1   *)
(* of the License, or (at your option) any later version.             *)
(*                                                                    *)
(* This program is distributed in the hope that it will be useful,    *)
(* but WITHOUT ANY WARRANTY; without even the implied warranty of     *)
(* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the      *)
(* GNU General Public License for more details.                       *)
(*                                                                    *)
(* You should have received a copy of the GNU Lesser General Public   *)
(* License along with this program; if not, write to the Free         *)
(* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA *)
(* 02110-1301 USA                                                     *)

(** %\subsection*{ support :  sums.v }%*)
Set Implicit Arguments.
Unset Strict Implicit.
Require Export Map_embed.
Require Export algebra_omissions.
From Algebra Require Export Sub_monoid.
Require Export more_syntax.

(** - A sequence of v1...vn of elements in a monoid V is a map (fin n)->V.
 We define the sum of a sequence of these elements.
 Of course we take the sum of an empty sequence to be zero

 %\label{sum}% *)
Fixpoint sum (V : monoid) (n : Nat) {struct n} : seq n V -> V :=
  match n return (seq n V -> V) with
  | O => fun x : seq 0 V => zero V
  | S m => fun x : seq (S m) V => head x +' sum (Seqtl x)
  end.

Lemma sum_comp :
 forall (M : monoid) (n : Nat) (f f' : seq n M),
 f =' f' in _ -> sum f =' sum f' in _.
induction n.
intros.
simpl in |- *.
apply Refl.
intros.
unfold sum in |- *.
apply SGROUP_comp; auto with algebra.
apply IHn.
change (Seqtl f =' Seqtl f' in _) in |- *.
apply Seqtl_comp; auto with algebra.
Qed.

Hint Resolve sum_comp: algebra.

Lemma sum_cons :
 forall (M : monoid) (m : M) (n : Nat) (f : seq n M),
 sum (m;; f) =' m +' sum f in _.
intros.
induction  n as [| n Hrecn].
simpl in |- *.
apply Refl.
unfold sum at 1 in |- *.
apply SGROUP_comp; auto with algebra.
apply sum_comp.
generalize dependent (Seqtl_cons_inv m f); intro p.
apply p.
Qed.

Hint Resolve sum_cons: algebra.

Lemma sum_concat :
 forall (n m : Nat) (G : monoid) (a : seq n G) (b : seq m G),
 sum (a ++ b) =' sum a +' sum b in _.
induction n.
intros.
apply Trans with ((zero G) +' sum b); auto with algebra.
intros.
apply Trans with (sum (hdtl a) +' sum b); auto with algebra.
apply Trans with (sum (hdtl a ++ b)); auto with algebra.
apply Trans with (sum (head a;; Seqtl a ++ b)).
unfold hdtl in |- *.
apply sum_comp; auto with algebra.
unfold hdtl in |- *.
apply Trans with (head a +' sum (Seqtl a ++ b)); auto with algebra.
apply Trans with (head a +' sum (Seqtl a) +' sum b); auto with algebra.
apply Trans with (head a +' (sum (Seqtl a) +' sum b)); auto with algebra.
Qed.

Hint Resolve sum_concat: algebra.

Lemma subtype_sum :
 forall (n : nat) (A : monoid) (B : submonoid A) (c : seq n B),
 subtype_elt (sum c) =' sum (Map_embed c) in _.
intros.
apply Sym.
induction n.
simpl in |- *.
auto with algebra.
apply Trans with (head (Map_embed c) +' sum (Seqtl (Map_embed c)));
 auto with algebra.
apply Trans with (subtype_elt (head c +' sum (Seqtl c))).
apply Trans with (subtype_elt (head c) +' subtype_elt (sum (Seqtl c)));
 auto with algebra.
apply Trans with (head (Map_embed c) +' sum (Map_embed (Seqtl c)));
 auto with algebra.
apply SGROUP_comp; auto with algebra.
apply sum_comp; auto with algebra.
apply Sym; auto with algebra.
generalize Map_embed_Seqtl.
intro p.
apply (p _ _ _ (c:seq _ _)).
apply subtype_elt_comp.
apply Sym; auto with algebra.
Qed.