(************************************************************************************)
(**                                                                                *)
(**                             The SQLEngines Library                              *)
(**                                                                                *)
(**            LRI, CNRS & Université Paris-Sud, Université Paris-Saclay            *)
(**                                                                                *)
(**                        Copyright 2016-2019 : FormalData                         *)
(**                                                                                *)
(**         Authors: Véronique Benzaken                                             *)
(**                  Évelyne Contejean                                              *)
(**                  Chantal Keller                                                 *)
(**                  Eunice-Raquel Martins                                          *)
(**                                                                                *)
(************************************************************************************)

Require Import List NArith.
Require Import ListFacts ListPermut Utils Signatures.
Require Import OrderedSet FiniteSet FiniteBag HighLevelSpec.

Set Implicit Arguments.

Section Filter_is_a_filter_op.
  Require Import Filter.

  Hypothesis A : Type.
  Hypothesis OA : Oeset.Rcd A.
  Hypothesis C : Cursor.Rcd OA.
  Hypothesis theta : A -> bool.
  Hypothesis theta_eq : forall x1 x2, Oeset.compare OA x1 x2 = Eq -> theta x1 = theta x2.

  Lemma mk_filter_is_a_filter_op :
    let F := (Filter.build _ theta_eq C) in
    is_a_filter_op OA (Cursor.materialize C) (Cursor.materialize F) theta (Filter.mk_filter F).
Proof.
intros F c t.
unfold Filter.mk_filter.
assert (Hc := Cursor.materialize_collection C c).
assert (Hf := Cursor.materialize_collection F c).
simpl in Hf; unfold Filter.collection in Hf.
apply eq_trans with (Oeset.nb_occ OA t (filter theta (Cursor.collection c))).
- apply permut_nb_occ; apply permut_refl_alt; assumption.
- rewrite Oeset.nb_occ_filter; [ | intros; apply theta_eq; assumption].
  case (theta t).
  + rewrite N.mul_1_r.
    apply sym_eq; apply permut_nb_occ; apply permut_refl_alt; assumption.
  + rewrite N.mul_0_r; apply refl_equal.
Qed.

End Filter_is_a_filter_op.

Section NestedLoop_is_a_join_op.
  Require Import NestedLoop.

  Hypotheses o1 o2 o : Type.
  Hypothesis O1 : Oeset.Rcd o1.
  Hypothesis O2 : Oeset.Rcd o2.
  Hypothesis O : Oeset.Rcd o.

  Hypothesis build_ : o1 -> o2 -> o.
  Hypothesis build_eq_1  :
    forall x1 x1' x2,
      Oeset.compare O1 x1 x1' = Eq -> Oeset.compare O (build_ x1 x2) (build_ x1' x2) = Eq.
  Hypothesis build_eq_2  :
    forall x1 x2 x2',
      Oeset.compare O2 x2 x2' = Eq -> Oeset.compare O (build_ x1 x2) (build_ x1 x2') = Eq.

  Hypothesis C1 : Cursor.Rcd O1.
  Hypothesis C2 : Cursor.Rcd O2.

  Hypothesis A : Type.
  Hypothesis OA : Oset.Rcd A.
  Hypothesis FA : Fset.Rcd OA.

  Hypothesis sort1 : Cursor.cursor C1 -> Fset.set FA.
  Hypothesis sort2 : Cursor.cursor C2 -> Fset.set FA.
  Hypothesis sup1 : o1 -> Fset.set FA.
  Hypothesis sup2 : o2 -> Fset.set FA.
  Hypothesis sup : o -> Fset.set FA.
  Hypothesis proj1 : o -> o1.
  Hypothesis proj2 : o -> o2.
  Hypothesis sa1 sa2: Fset.set FA.
  Hypothesis sup_eq : forall t1 t2, Oeset.compare O t1 t2 = Eq -> sup t1 =S= sup t2.
  Hypothesis sup_build : forall t1 t2, sup (build_ t1 t2) =S= (sup1 t1 unionS sup2 t2).
  Hypothesis sup_proj1 : forall t, sup1 (proj1 t) =S= sa1.
  Hypothesis sup_proj2 : forall t, sup2 (proj2 t) =S= sa2.
  Hypothesis build_proj :
    forall t, sup t =S= (sa1 unionS sa2) -> Oeset.compare O t (build_ (proj1 t) (proj2 t)) = Eq.
  Hypothesis build_eq :
    forall t1 u1 t2 u2, Oeset.compare O1 t1 u1 = Eq -> Oeset.compare O2 t2 u2 = Eq ->
                        Oeset.compare O (build_ t1 t2) (build_ u1 u2) = Eq.
  Hypothesis build_split_eq_1 : forall t1 u1 t2 u2,
      Oeset.compare O (build_ t1 t2) (build_ u1 u2) = Eq ->
      sup1 t1 =S= sup1 u1 -> sup2 t2 =S= sup2 u2 ->
      Oeset.compare O1 t1 u1 = Eq.
  Hypothesis build_split_eq_2 : forall t1 u1 t2 u2,
      Oeset.compare O (build_ t1 t2) (build_ u1 u2) = Eq ->
      sup1 t1 =S= sa1 -> sup1 t1 =S= sup1 u1 -> sup2 t2 =S= sa2 -> sup2 t2 =S= sup2 u2 ->
      Oeset.compare O2 t2 u2 = Eq.

  Notation NL := (NestedLoop.build O build_ build_eq_1 build_eq_2 C1 C2).

  Lemma NL_is_a_join_op :
    is_a_join_op O1 O2 O (Cursor.materialize C1) (Cursor.materialize C2) (Cursor.materialize NL)
                  FA sup1 sup2 sup proj1 proj2 sa1 sa2
                 (fun c1 c2 => NestedLoop.mk_cursor C1 C2 nil c1 c2).
Proof.
  unfold is_a_join_op.
  intros c1 c2 H1 H2; split.
  - intros t Ht.
    assert (Kt : Oeset.mem_bool
                   O t (flat_map (fun a : o1 => map (build_ a) (Cursor.collection c2))
                                 (Cursor.collection c1)) = true).
    {
      rewrite <- Oeset.nb_occ_diff_0_pos in Ht.
      generalize (Oeset.nb_occ_mem _ _ _ Ht); clear Ht; intro Ht.
      rewrite <- Ht; apply sym_eq.
      apply Oeset.mem_bool_eq_2.
      apply Cursor.materialize_collection.
    }
    rewrite Oeset.mem_bool_true_iff in Kt.
    destruct Kt as [t' [Kt Ht']].
    rewrite in_flat_map in Ht'.
    destruct Ht' as [x1 [Hx1 Ht']].
    rewrite in_map_iff in Ht'.
    destruct Ht' as [x2 [Ht' Hx2]]; subst t'.
    rewrite (Fset.equal_eq_1 _ _ _ _ (sup_eq _ _ Kt)).
    rewrite (Fset.equal_eq_1 _ _ _ _ (sup_build _ _)).
    apply Fset.union_eq.
    + apply H1.
      assert (Kx1 : Oeset.mem_bool O1 x1 (Cursor.collection c1) = true).
      {
        rewrite Oeset.mem_bool_true_iff; exists x1; split; [apply Oeset.compare_eq_refl | assumption].
      }
      rewrite <- Oeset.nb_occ_diff_0_pos.
      apply Oeset.mem_nb_occ.
      rewrite <- Kx1; apply Oeset.mem_bool_eq_2.
      apply Cursor.materialize_collection.
    + apply H2.
      rewrite <- Oeset.nb_occ_diff_0_pos.
      apply Oeset.mem_nb_occ.
      assert (Kx2 : Oeset.mem_bool O2 x2 (Cursor.collection c2) = true).
      {
        rewrite Oeset.mem_bool_true_iff; exists x2; split; [apply Oeset.compare_eq_refl | assumption].
      }
      rewrite <- Kx2; apply Oeset.mem_bool_eq_2.
      apply Cursor.materialize_collection.
  - intros t Ht.
    assert (Kt : Oeset.nb_occ
                   O t
                   (Cursor.materialize
                      NL
                      {| NestedLoop.visited := nil; NestedLoop.outer := c1; NestedLoop.inner := c2 |}) =
                 Oeset.nb_occ O t (flat_map (fun a : o1 => map (build_ a) (Cursor.collection c2))
                                 (Cursor.collection c1))).
    {
      apply Oeset.nb_occ_eq_2.
      apply Cursor.materialize_collection.
    }
    rewrite Kt.
    apply trans_eq with
        (Oeset.nb_occ
           O (build_ (proj1 t) (proj2 t))
           (flat_map (fun a : o1 => map (build_ a) (Cursor.collection c2)) (Cursor.collection c1))).
    + apply Oeset.nb_occ_eq_1.
      apply build_proj; assumption.
    + apply trans_eq with
          (Oeset.nb_occ O1 (proj1 t) (Cursor.collection c1) *
           Oeset.nb_occ O2 (proj2 t) (Cursor.collection c2)).
      * assert (Sx1 := sup_proj1 t); set (x1 := proj1 t) in *; clearbody x1.
        assert (Sx2 := sup_proj2 t); set (x2 := proj2 t) in *; clearbody x2.
        assert (Hl1 : forall y1, In y1 (Cursor.collection c1) -> sup1 y1 =S= sa1).
        {
          intros y1 Hy1.
          apply H1.
          assert (Ky1 : Oeset.mem_bool O1 y1 (Cursor.collection c1) = true).
          {
            rewrite Oeset.mem_bool_true_iff; exists y1; split; [ | assumption].
            apply Oeset.compare_eq_refl.
          }
          rewrite <- Oeset.nb_occ_diff_0_pos.
          apply Oeset.mem_nb_occ; rewrite <- Ky1; apply Oeset.mem_bool_eq_2.
          apply Cursor.materialize_collection.
        }
        assert (Hl2 : forall y2, In y2 (Cursor.collection c2) -> sup2 y2 =S= sa2).
        {
          intros y2 Hy2.
          apply H2.
          assert (Ky2 : Oeset.mem_bool O2 y2 (Cursor.collection c2) = true).
          {
            rewrite Oeset.mem_bool_true_iff; exists y2; split; [ | assumption].
            apply Oeset.compare_eq_refl.
          }
          rewrite <- Oeset.nb_occ_diff_0_pos.
          apply Oeset.mem_nb_occ; rewrite <- Ky2; apply Oeset.mem_bool_eq_2.
          apply Cursor.materialize_collection.
        }
        set (l1 := (Cursor.collection c1)) in *; clearbody l1.
        set (l2 := (Cursor.collection c2)) in *; clearbody l2.
        clear Kt; revert l2 Hl2; induction l1 as [ | a1 l1]; intros l2 Hl2; simpl; trivial.
        rewrite Oeset.nb_occ_app, IHl1; [ | intros; apply Hl1; right; assumption | assumption].
        {
          case_eq (Oeset.compare O1 x1 a1); intro Hx1.
          - rewrite Nmult_plus_distr_r, N.mul_1_l; apply f_equal2; [ | apply refl_equal].
            induction l2 as [ | a2 l2]; [apply refl_equal | ].
            simpl; rewrite IHl2; [ | intros; apply Hl2; right; trivial].
            apply f_equal2; [ | apply refl_equal].
            case_eq (Oeset.compare O2 x2 a2); intro Hx2.
            + rewrite (build_eq _ _ _ _ Hx1 Hx2); trivial.
            + case_eq (Oeset.compare O (build_ x1 x2) (build_ a1 a2)); intro Hx; trivial.
              assert (Abs : Oeset.compare O2 x2 a2 = Eq).
              {
                apply (build_split_eq_2 _ _ _ _ Hx).
                - rewrite (Fset.equal_eq_1 _ _ _ _ Sx1).
                  apply Fset.equal_refl.
                - rewrite (Fset.equal_eq_1 _ _ _ _ Sx1).
                  rewrite <- (Fset.equal_eq_1 _ _ _ _ (Hl1 _ (or_introl _ (refl_equal _)))).
                  apply Fset.equal_refl.
                - rewrite (Fset.equal_eq_1 _ _ _ _ Sx2).
                  apply Fset.equal_refl.
                - rewrite (Fset.equal_eq_1 _ _ _ _ Sx2).
                  rewrite <- (Fset.equal_eq_1 _ _ _ _ (Hl2 _ (or_introl _ (refl_equal _)))).
                  apply Fset.equal_refl.
              }
              rewrite Abs in Hx2; discriminate Hx2.
            + case_eq (Oeset.compare O (build_ x1 x2) (build_ a1 a2)); intro Hx; trivial.
              assert (Abs : Oeset.compare O2 x2 a2 = Eq).
              {
                apply (build_split_eq_2 _ _ _ _ Hx).
                - rewrite (Fset.equal_eq_1 _ _ _ _ Sx1).
                  apply Fset.equal_refl.
                - rewrite (Fset.equal_eq_1 _ _ _ _ Sx1).
                  rewrite <- (Fset.equal_eq_1 _ _ _ _ (Hl1 _ (or_introl _ (refl_equal _)))).
                  apply Fset.equal_refl.
                - rewrite (Fset.equal_eq_1 _ _ _ _ Sx2).
                  apply Fset.equal_refl.
                - rewrite (Fset.equal_eq_1 _ _ _ _ Sx2).
                  rewrite <- (Fset.equal_eq_1 _ _ _ _ (Hl2 _ (or_introl _ (refl_equal _)))).
                  apply Fset.equal_refl.
              }
              rewrite Abs in Hx2; discriminate Hx2.
          - rewrite Nmult_plus_distr_r; apply f_equal2; [ | apply refl_equal].
            rewrite N.mul_0_l.
            induction l2 as [ | a2 l2]; [apply refl_equal | ].
            simpl; rewrite IHl2; [ | intros; apply Hl2; right; trivial].
            case_eq (Oeset.compare O (build_ x1 x2) (build_ a1 a2)); intro Hx; trivial.
            assert (Abs : Oeset.compare O1 x1 a1 = Eq).
            {
              apply (build_split_eq_1 _ _ _ _ Hx).
              - rewrite (Fset.equal_eq_1 _ _ _ _ Sx1).
                rewrite <- (Fset.equal_eq_1 _ _ _ _ (Hl1 _ (or_introl _ (refl_equal _)))).
                apply Fset.equal_refl.
              - rewrite (Fset.equal_eq_1 _ _ _ _ Sx2).
                rewrite <- (Fset.equal_eq_1 _ _ _ _ (Hl2 _ (or_introl _ (refl_equal _)))).
                apply Fset.equal_refl.
            }
            rewrite Abs in Hx1; discriminate Hx1.
          - rewrite Nmult_plus_distr_r; apply f_equal2; [ | apply refl_equal].
            rewrite N.mul_0_l.
            induction l2 as [ | a2 l2]; [apply refl_equal | ].
            simpl; rewrite IHl2; [ | intros; apply Hl2; right; trivial].
            case_eq (Oeset.compare O (build_ x1 x2) (build_ a1 a2)); intro Hx; trivial.
            assert (Abs : Oeset.compare O1 x1 a1 = Eq).
            {
              apply (build_split_eq_1 _ _ _ _ Hx).
              - rewrite (Fset.equal_eq_1 _ _ _ _ Sx1).
                rewrite <- (Fset.equal_eq_1 _ _ _ _ (Hl1 _ (or_introl _ (refl_equal _)))).
                apply Fset.equal_refl.
              - rewrite (Fset.equal_eq_1 _ _ _ _ Sx2).
                rewrite <- (Fset.equal_eq_1 _ _ _ _ (Hl2 _ (or_introl _ (refl_equal _)))).
                apply Fset.equal_refl.
            }
            rewrite Abs in Hx1; discriminate Hx1.
        }
      * {
          apply f_equal2; apply sym_eq; apply Oeset.nb_occ_eq_2.
          - apply Cursor.materialize_collection.
          - apply Cursor.materialize_collection.
        }
Qed.

End NestedLoop_is_a_join_op.

Section BlockNestedLoop_is_a_join_op.
  Require Import BlockNestedLoop.

  Hypotheses o1 o2 o : Type.
  Hypothesis O1 : Oeset.Rcd o1.
  Hypothesis O2 : Oeset.Rcd o2.
  Hypothesis O : Oeset.Rcd o.

  Hypothesis build_ : o1 -> o2 -> o.
  Hypothesis build_eq_1  :
    forall x1 x1' x2,
      Oeset.compare O1 x1 x1' = Eq -> Oeset.compare O (build_ x1 x2) (build_ x1' x2) = Eq.
  Hypothesis build_eq_2  :
    forall x1 x2 x2',
      Oeset.compare O2 x2 x2' = Eq -> Oeset.compare O (build_ x1 x2) (build_ x1 x2') = Eq.

  Definition OL1 := mk_oelists O1.
  Definition OL2 := mk_oelists O2.
  Definition OL := mk_oelists O.

  Hypothesis C1 : Cursor.Rcd OL1.
  Hypothesis C2 : Cursor.Rcd OL2.

  Hypothesis A : Type.
  Hypothesis OA : Oset.Rcd A.
  Hypothesis FA : Fset.Rcd OA.

  Hypothesis sort1 : Cursor.cursor C1 -> Fset.set FA.
  Hypothesis sort2 : Cursor.cursor C2 -> Fset.set FA.
  Hypothesis sup1 : o1 -> Fset.set FA.
  Hypothesis sup2 : o2 -> Fset.set FA.
  Hypothesis sup : o -> Fset.set FA.
  Hypothesis proj1 : o -> o1.
  Hypothesis proj2 : o -> o2.
  Hypothesis sa1 sa2: Fset.set FA.
  Hypothesis sup_eq : forall t1 t2, Oeset.compare O t1 t2 = Eq -> sup t1 =S= sup t2.
  Hypothesis sup_build : forall t1 t2, sup (build_ t1 t2) =S= (sup1 t1 unionS sup2 t2).
  Hypothesis sup_proj1 : forall t, sup1 (proj1 t) =S= sa1.
  Hypothesis sup_proj2 : forall t, sup2 (proj2 t) =S= sa2.
  Hypothesis build_proj :
    forall t, sup t =S= (sa1 unionS sa2) -> Oeset.compare O t (build_ (proj1 t) (proj2 t)) = Eq.
  Hypothesis build_eq :
    forall t1 u1 t2 u2, Oeset.compare O1 t1 u1 = Eq -> Oeset.compare O2 t2 u2 = Eq ->
                        Oeset.compare O (build_ t1 t2) (build_ u1 u2) = Eq.
  Hypothesis build_split_eq_1 : forall t1 u1 t2 u2,
      Oeset.compare O (build_ t1 t2) (build_ u1 u2) = Eq ->
      sup1 t1 =S= sup1 u1 -> sup2 t2 =S= sup2 u2 ->
      Oeset.compare O1 t1 u1 = Eq.
  Hypothesis build_split_eq_2 : forall t1 u1 t2 u2,
      Oeset.compare O (build_ t1 t2) (build_ u1 u2) = Eq ->
      sup1 t1 =S= sa1 -> sup1 t1 =S= sup1 u1 -> sup2 t2 =S= sa2 -> sup2 t2 =S= sup2 u2 ->
      Oeset.compare O2 t2 u2 = Eq.

  Notation BNL := (BlockNestedLoop.build O build_ build_eq_1 build_eq_2 C1 C2).

  Lemma BNL_is_a_join_op :
    is_a_join_op O1 O2 O (fun c1 => flat_map (fun x => x) (Cursor.materialize C1 c1))
                 (fun c2 => flat_map (fun x => x) (Cursor.materialize C2 c2))
                 (fun c => flat_map (fun x => x) (Cursor.materialize BNL c))
                 _ sup1 sup2 sup proj1 proj2 sa1 sa2
                 (fun c1 c2 => NestedLoop.mk_cursor C1 C2 nil c1 c2).
Proof.
  unfold is_a_join_op.
  intros c1 c2 H1 H2; split.
  - intros t Ht.
    assert (Kt : Oeset.mem_bool
                   O t (flat_map
                          (fun x : list o => x)
                          (@Cursor.collection _ _ BNL (NestedLoop.mk_cursor C1 C2 nil c1 c2))) = true).
    {
      rewrite <- Oeset.nb_occ_diff_0_pos in Ht.
      generalize (Oeset.nb_occ_mem _ _ _ Ht); clear Ht; intro Ht.
      rewrite <- Ht; apply sym_eq.
      apply Oeset.mem_bool_eq_2; apply comparelA_comparelA_eq.
      apply (@Cursor.materialize_collection _ _ BNL (NestedLoop.mk_cursor C1 C2 nil c1 c2)).
    }
    simpl in Kt; unfold NestedLoop.collection in Kt; simpl in Kt.
    assert (Aux := mem_permut_mem_strong
                     O t (permut_flat_map build_ (Cursor.collection c1) (Cursor.collection c2))).
    unfold cartesian_product in Aux; unfold BlockNestedLoop.OL1, BlockNestedLoop.OL2 in Kt;
      unfold OL1, OL2 in Aux; simpl in Aux, Kt.
    rewrite <- Aux in Kt; clear Aux.
    rewrite Oeset.mem_bool_true_iff in Kt.
    destruct Kt as [t' [Kt Ht']]; rewrite (Fset.equal_eq_1 _ _ _ _ (sup_eq _ _ Kt)).
    rewrite in_flat_map in Ht'.
    destruct Ht' as [x1 [Hx1 Ht']].
    simpl in Hx1; unfold NestedLoop.collection in Hx1.
    rewrite in_flat_map in Hx1.
    destruct Hx1 as [l1 [Hl1 Hx1]]; simpl in Hl1.
    rewrite in_map_iff in Ht'; destruct Ht' as [x2 [Ht' Hx2]].
    rewrite in_flat_map in Hx2; destruct Hx2 as [l2 [Hl2 Hx2]]; subst t'.
    rewrite (Fset.equal_eq_1 _ _ _ _ (sup_build _ _)); apply Fset.union_eq.
    + apply H1.
      rewrite <- Oeset.nb_occ_diff_0_pos.
      apply Oeset.mem_nb_occ.
      rewrite (Oeset.mem_bool_eq_2 _ x1 _ (flat_map (fun x => x) (Cursor.collection c1))).
      * rewrite Oeset.mem_bool_true_iff; exists x1; split; [apply Oeset.compare_eq_refl | ].
        rewrite in_flat_map; exists l1; split; trivial.
      * apply comparelA_comparelA_eq.
        assert (Aux := Cursor.materialize_collection C1 c1); simpl in Aux; apply Aux.
    + apply H2.
      rewrite <- Oeset.nb_occ_diff_0_pos.
      apply Oeset.mem_nb_occ.
      rewrite (Oeset.mem_bool_eq_2 _ x2 _ (flat_map (fun x => x) (Cursor.collection c2))).
      * rewrite Oeset.mem_bool_true_iff; exists x2; split; [apply Oeset.compare_eq_refl | ].
        rewrite in_flat_map; exists l2; split; trivial.
      * apply comparelA_comparelA_eq.
        assert (Aux := Cursor.materialize_collection C2 c2); simpl in Aux; apply Aux.
  - intros t Ht.
    assert (Kt :  Oeset.nb_occ
                    O t
                    (flat_map
                       (fun x : list o => x)
                       (Cursor.materialize
                          BNL {| NestedLoop.visited := nil;
                                 NestedLoop.outer := c1;
                                 NestedLoop.inner := c2 |})) =
                  Oeset.nb_occ
                    O t
                    (flat_map
                       (fun x : list o => x)
                       (@Cursor.collection
                          _ _ BNL {| NestedLoop.visited := nil;
                                 NestedLoop.outer := c1;
                                 NestedLoop.inner := c2 |}))).
    {
      apply Oeset.nb_occ_eq_2.
      apply comparelA_comparelA_eq.
      assert (Aux := @Cursor.materialize_collection _ _ BNL (NestedLoop.mk_cursor C1 C2 nil c1 c2)).
      simpl in Aux; unfold Cursor.collection; simpl; apply Aux.
    }
    rewrite Kt.
    apply trans_eq with
        (Oeset.nb_occ
           O (build_ (proj1 t) (proj2 t))
           (flat_map (fun x : list o => x)
       (@Cursor.collection _ _ BNL
                           {| NestedLoop.visited := nil; NestedLoop.outer := c1; NestedLoop.inner := c2 |}))).
    + apply Oeset.nb_occ_eq_1.
      apply build_proj; assumption.
    + apply trans_eq with
          (Oeset.nb_occ O1 (proj1 t) (flat_map (fun x => x) (Cursor.collection c1)) *
           Oeset.nb_occ O2 (proj2 t) (flat_map (fun x => x) (Cursor.collection c2))).
      * assert (Sx1 := sup_proj1 t); set (x1 := proj1 t) in *; clearbody x1.
        assert (Sx2 := sup_proj2 t); set (x2 := proj2 t) in *; clearbody x2.
        assert (Hl1 : forall y1, In y1 (flat_map (fun x => x) (Cursor.collection c1)) -> sup1 y1 =S= sa1).
        {
          intros y1 Hy1.
          apply H1.
          assert (Ky1 : Oeset.mem_bool O1 y1 (flat_map (fun x => x) (Cursor.collection c1)) = true).
          {
            rewrite Oeset.mem_bool_true_iff; exists y1; split; [ | assumption].
            apply Oeset.compare_eq_refl.
          }
          rewrite <- Oeset.nb_occ_diff_0_pos.
          apply Oeset.mem_nb_occ; rewrite <- Ky1; apply Oeset.mem_bool_eq_2.
          apply comparelA_comparelA_eq.
          assert (Aux := Cursor.materialize_collection C1 c1); unfold OL1 in Aux; simpl in Aux.
          apply Aux.
        }
        assert (Hl2 : forall y2, In y2 (flat_map (fun x => x) (Cursor.collection c2)) -> sup2 y2 =S= sa2).
        {
          intros y2 Hy2.
          apply H2.
          assert (Ky2 : Oeset.mem_bool O2 y2 (flat_map (fun x => x) (Cursor.collection c2)) = true).
          {
            rewrite Oeset.mem_bool_true_iff; exists y2; split; [ | assumption].
            apply Oeset.compare_eq_refl.
          }
          rewrite <- Oeset.nb_occ_diff_0_pos.
          apply Oeset.mem_nb_occ; rewrite <- Ky2; apply Oeset.mem_bool_eq_2.
          apply comparelA_comparelA_eq.
          assert (Aux := Cursor.materialize_collection C2 c2); unfold OL2 in Aux; simpl in Aux.
          apply Aux.
        }
        clear Kt; simpl; unfold NestedLoop.collection in *; simpl.
        unfold BlockNestedLoop.OL1, BlockNestedLoop.OL2, OL1, OL2 in *; simpl.
        set (l1 := (@Cursor.collection (list o1) (@mk_oelists o1 O1) C1 c1)) in *;
          clearbody l1.
        set (l2 := (@Cursor.collection (list o2) (@mk_oelists o2 O2) C2 c2)) in *;
          clearbody l2.
        revert l2 Hl2; induction l1 as [ | a1 l1]; intros l2 Hl2; simpl; trivial.
        {
          rewrite flat_map_app, 2 Oeset.nb_occ_app, IHl1.
          - rewrite Nmult_plus_distr_r; apply f_equal2; [ | apply refl_equal].
            simpl in Hl1.
            assert (Ha1 : forall y1, In y1 a1 -> sup1 y1 =S= sa1).
            {
              intros y1 Hy1; apply Hl1; apply in_or_app; left; assumption.
            }
            clear l1 IHl1 Hl1.
            induction l2 as [ | a2 l2]; simpl; [rewrite N.mul_0_r; apply refl_equal | ].
            rewrite 2 Oeset.nb_occ_app, Nmult_plus_distr_l, IHl2.
            + apply f_equal2; [ | apply refl_equal].
              assert (Ha2 : forall y2, In y2 a2 -> sup2 y2 =S= sa2).
              {
                intros y2 Hy2; apply Hl2; simpl; apply in_or_app; left; assumption.
              }
              clear l2 Hl2 IHl2.
              revert a2 Ha2.
              induction a1 as [ | a1 l1]; intros l2 Hl2; simpl; trivial.
              rewrite Oeset.nb_occ_app, IHl1, Nmult_plus_distr_r.
              * apply f_equal2; [ | apply refl_equal].
                {
                  case_eq (Oeset.compare O1 x1 a1); intro Hx1.
                  - rewrite N.mul_1_l.
                    induction l2 as [ | a2 l2]; [apply refl_equal | ].
                    simpl; rewrite IHl2; [ | intros; apply Hl2; right; trivial].
                    apply f_equal2; [ | apply refl_equal].
                    case_eq (Oeset.compare O2 x2 a2); intro Hx2.
                    + rewrite (build_eq _ _ _ _ Hx1 Hx2); trivial.
                    + case_eq (Oeset.compare O (build_ x1 x2) (build_ a1 a2)); intro Hx; trivial.
                      assert (Abs : Oeset.compare O2 x2 a2 = Eq).
                      {
                        apply (build_split_eq_2 _ _ _ _ Hx).
                        - rewrite (Fset.equal_eq_1 _ _ _ _ Sx1).
                          apply Fset.equal_refl.
                        - rewrite (Fset.equal_eq_1 _ _ _ _ Sx1).
                          rewrite <- (Fset.equal_eq_1 _ _ _ _ (Ha1 _ (or_introl _ (refl_equal _)))).
                          apply Fset.equal_refl.
                        - rewrite (Fset.equal_eq_1 _ _ _ _ Sx2).
                          apply Fset.equal_refl.
                        - rewrite (Fset.equal_eq_1 _ _ _ _ Sx2).
                          rewrite <- (Fset.equal_eq_1 _ _ _ _ (Hl2 _ (or_introl _ (refl_equal _)))).
                          apply Fset.equal_refl.
                      }
                      rewrite Abs in Hx2; discriminate Hx2.
                    + case_eq (Oeset.compare O (build_ x1 x2) (build_ a1 a2)); intro Hx; trivial.
                      assert (Abs : Oeset.compare O2 x2 a2 = Eq).
                      {
                        apply (build_split_eq_2 _ _ _ _ Hx).
                        - rewrite (Fset.equal_eq_1 _ _ _ _ Sx1).
                          apply Fset.equal_refl.
                        - rewrite (Fset.equal_eq_1 _ _ _ _ Sx1).
                          rewrite <- (Fset.equal_eq_1 _ _ _ _ (Ha1 _ (or_introl _ (refl_equal _)))).
                          apply Fset.equal_refl.
                        - rewrite (Fset.equal_eq_1 _ _ _ _ Sx2).
                          apply Fset.equal_refl.
                        - rewrite (Fset.equal_eq_1 _ _ _ _ Sx2).
                          rewrite <- (Fset.equal_eq_1 _ _ _ _ (Hl2 _ (or_introl _ (refl_equal _)))).
                          apply Fset.equal_refl.
                      }
                      rewrite Abs in Hx2; discriminate Hx2.
                  - rewrite N.mul_0_l.
                    induction l2 as [ | a2 l2]; [apply refl_equal | ].
                    simpl; rewrite IHl2; [ | intros; apply Hl2; right; trivial].
                    case_eq (Oeset.compare O (build_ x1 x2) (build_ a1 a2)); intro Hx; trivial.
                    assert (Abs : Oeset.compare O1 x1 a1 = Eq).
                    {
                      apply (build_split_eq_1 _ _ _ _ Hx).
                      - rewrite (Fset.equal_eq_1 _ _ _ _ Sx1).
                        rewrite <- (Fset.equal_eq_1 _ _ _ _ (Ha1 _ (or_introl _ (refl_equal _)))).
                        apply Fset.equal_refl.
                      - rewrite (Fset.equal_eq_1 _ _ _ _ Sx2).
                        rewrite <- (Fset.equal_eq_1 _ _ _ _ (Hl2 _ (or_introl _ (refl_equal _)))).
                        apply Fset.equal_refl.
                    }
                    rewrite Abs in Hx1; discriminate Hx1.
                  - rewrite N.mul_0_l.
                    induction l2 as [ | a2 l2]; [apply refl_equal | ].
                    simpl; rewrite IHl2; [ | intros; apply Hl2; right; trivial].
                    case_eq (Oeset.compare O (build_ x1 x2) (build_ a1 a2)); intro Hx; trivial.
                    assert (Abs : Oeset.compare O1 x1 a1 = Eq).
                    {
                      apply (build_split_eq_1 _ _ _ _ Hx).
                      - rewrite (Fset.equal_eq_1 _ _ _ _ Sx1).
                        rewrite <- (Fset.equal_eq_1 _ _ _ _ (Ha1 _ (or_introl _ (refl_equal _)))).
                        apply Fset.equal_refl.
                      - rewrite (Fset.equal_eq_1 _ _ _ _ Sx2).
                        rewrite <- (Fset.equal_eq_1 _ _ _ _ (Hl2 _ (or_introl _ (refl_equal _)))).
                        apply Fset.equal_refl.
            }
            rewrite Abs in Hx1; discriminate Hx1.
        }
      * intros; apply Ha1; right; assumption.
      * assumption.
    + intros y2 Hy2; apply Hl2.
      simpl; apply in_or_app; right; assumption.
  - intros y1 Hy1; apply Hl1.
    simpl; apply in_or_app; right; assumption.
  - assumption.
  }
  * {
      apply f_equal2; apply sym_eq; apply Oeset.nb_occ_eq_2.
      - apply comparelA_comparelA_eq.
        assert (Aux := @Cursor.materialize_collection _ _ C1 c1).
        simpl in Aux; unfold Cursor.collection; simpl; apply Aux.
      - apply comparelA_comparelA_eq.
        assert (Aux := @Cursor.materialize_collection _ _ C2 c2).
        simpl in Aux; unfold Cursor.collection; simpl; apply Aux.
    }
Qed.

End BlockNestedLoop_is_a_join_op.

Section ThetaNestedLoop_is_a_filter_join_op.
Require Import ThetaNestedLoop.
  Hypotheses o1 o2 o : Type.
  Hypothesis O1 : Oeset.Rcd o1.
  Hypothesis O2 : Oeset.Rcd o2.
  Hypothesis O : Oeset.Rcd o.

  Hypothesis build_ : o1 -> o2 -> o.
  Hypothesis build_eq_1  :
    forall x1 x1' x2,
      Oeset.compare O1 x1 x1' = Eq -> Oeset.compare O (build_ x1 x2) (build_ x1' x2) = Eq.
  Hypothesis build_eq_2  :
    forall x1 x2 x2',
      Oeset.compare O2 x2 x2' = Eq -> Oeset.compare O (build_ x1 x2) (build_ x1 x2') = Eq.
  Hypothesis theta : o -> bool.
  Hypothesis theta_eq : forall x y, Oeset.compare O x y = Eq -> theta x = theta y.

  Hypothesis C1 : Cursor.Rcd O1.
  Hypothesis C2 : Cursor.Rcd O2.

  Hypothesis A : Type.
  Hypothesis OA : Oset.Rcd A.
  Hypothesis FA : Fset.Rcd OA.

  Hypothesis sort1 : Cursor.cursor C1 -> Fset.set FA.
  Hypothesis sort2 : Cursor.cursor C2 -> Fset.set FA.
  Hypothesis sup1 : o1 -> Fset.set FA.
  Hypothesis sup2 : o2 -> Fset.set FA.
  Hypothesis sup : o -> Fset.set FA.
  Hypothesis proj1_ : o -> o1.
  Hypothesis proj2_ : o -> o2.
  Hypothesis sa1 sa2: Fset.set FA.
  Hypothesis sup_eq : forall t1 t2, Oeset.compare O t1 t2 = Eq -> sup t1 =S= sup t2.
  Hypothesis sup_build : forall t1 t2, sup (build_ t1 t2) =S= (sup1 t1 unionS sup2 t2).
  Hypothesis sup_proj1 : forall t, sup1 (proj1_ t) =S= sa1.
  Hypothesis sup_proj2 : forall t, sup2 (proj2_ t) =S= sa2.
  Hypothesis build_proj :
    forall t, sup t =S= (sa1 unionS sa2) -> Oeset.compare O t (build_ (proj1_ t) (proj2_ t)) = Eq.
  Hypothesis build_eq :
    forall t1 u1 t2 u2, Oeset.compare O1 t1 u1 = Eq -> Oeset.compare O2 t2 u2 = Eq ->
                        Oeset.compare O (build_ t1 t2) (build_ u1 u2) = Eq.
  Hypothesis build_split_eq_1 : forall t1 u1 t2 u2,
      Oeset.compare O (build_ t1 t2) (build_ u1 u2) = Eq ->
      sup1 t1 =S= sup1 u1 -> sup2 t2 =S= sup2 u2 ->
      Oeset.compare O1 t1 u1 = Eq.
  Hypothesis build_split_eq_2 : forall t1 u1 t2 u2,
      Oeset.compare O (build_ t1 t2) (build_ u1 u2) = Eq ->
      sup1 t1 =S= sa1 -> sup1 t1 =S= sup1 u1 -> sup2 t2 =S= sa2 -> sup2 t2 =S= sup2 u2 ->
      Oeset.compare O2 t2 u2 = Eq.

  Notation TNL := (ThetaNestedLoop.build O build_ build_eq_1 build_eq_2 _ theta_eq C1 C2).

  Lemma TNL_is_a_filter_join_op :
    is_a_filter_join_op
      O1 O2 O (Cursor.materialize C1) (Cursor.materialize C2) (Cursor.materialize TNL)
      _ sup1 sup2 sup proj1_ proj2_ theta sa1 sa2
      (fun c1 c2 => NestedLoop.mk_cursor C1 C2 nil c1 c2).
    Proof.
      intros c1 c2  H1 H2.
      assert (HJ := NL_is_a_join_op
                     O _ build_eq_1 build_eq_2 C1 C2 FA
                     _ _ _ _ _ sa1 sa2 sup_eq sup_build sup_proj1 sup_proj2
                     build_proj build_eq build_split_eq_1 build_split_eq_2 _ _ H1 H2).
      assert (HF := mk_filter_is_a_filter_op
                      (NestedLoop.build O build_ build_eq_1 build_eq_2 C1 C2)
                      _ theta_eq (NestedLoop.mk_cursor C1 C2 nil c1 c2)).
      split.
      - intros t Ht.
        apply ((proj1 HJ) t).
        assert (Kt := HF t); simpl in Kt.
        unfold ThetaNestedLoop.build in Ht; unfold Filter.mk_filter in Kt.
        rewrite Kt in Ht.
        destruct (theta t).
        + rewrite N.mul_1_r in Ht; apply Ht.
        + rewrite N.mul_0_r in Ht; discriminate Ht.
      - intros t Ht.
        assert (Kt := HF t); simpl in Kt.
        unfold ThetaNestedLoop.build; unfold Filter.mk_filter in Kt.
        rewrite Kt, (proj2 HJ t Ht).
        apply refl_equal.
    Qed.

End ThetaNestedLoop_is_a_filter_join_op.

(* Any index scan is a filter *)

Section IndexScan_is_a_filter_op.

Variable o1 o2 : Type.
Variable O1 : Oeset.Rcd o1.
Variable O2 : Oeset.Rcd o2.

Variable IS : Index.Rcd O1 O2.

Lemma IS_is_a_filter_op :
    forall (x1 : o1),
    is_a_filter_op O1 (Index.c1 IS)
    (Cursor.materialize (Index.C1 IS))
    (fun x => Index.P IS (Index.proj IS x1) (Index.proj IS x))
    (fun c => Index.i IS c (Index.proj IS x1)).
Proof.
intros x1 c x; simpl.
rewrite (Oeset.nb_occ_eq_2 _ _ _ _ (Cursor.materialize_collection _ _)).
rewrite (Oeset.nb_occ_eq_2 _ _ _ _ (Index.i_collection IS _ _)).
rewrite Oeset.nb_occ_filter.
- case_eq (Index.P IS (Index.proj IS x1) (Index.proj IS x)); intro Hx;
    [ | rewrite N.mul_0_r; apply refl_equal].
  rewrite N.mul_1_r.
  apply refl_equal.
- intros y1 y2 _ H.
  apply (Index.P_eq_2 IS); apply (Index.proj_eq IS); assumption.
Qed.

End IndexScan_is_a_filter_op.

Section IndexJoin_is_a_filter_join_op.

Require Import IndexJoin.

Variable o1 o2 op o : Type.
Hypothesis O1 : Oeset.Rcd o1.
Hypothesis O2 : Oeset.Rcd o2.
Hypothesis OP : Oeset.Rcd op.
Hypothesis O : Oeset.Rcd o.

Hypothesis projS1 : o1 -> op.
Hypothesis projS1_eq :
  forall x1 x2, Oeset.compare O1 x1 x2 = Eq -> Oeset.compare OP (projS1 x1) (projS1 x2) = Eq.

Hypothesis S1 : Cursor.Rcd O1.
Hypothesis S2 : Index.Rcd O2 OP.
Hypothesis build_ : o1 -> o2 -> o.
Hypothesis build_eq_1  :
  forall x1 x1' x2,
    Oeset.compare O1 x1 x1' = Eq -> Oeset.compare O (build_ x1 x2) (build_ x1' x2) = Eq.
Hypothesis build_eq_2  :
  forall x1 x2 x2',
    Oeset.compare O2 x2 x2' = Eq -> Oeset.compare O (build_ x1 x2) (build_ x1 x2') = Eq.

  Hypothesis A : Type.
  Hypothesis OA : Oset.Rcd A.
  Hypothesis FA : Fset.Rcd OA.

  Hypothesis sort1 : Cursor.cursor S1 -> Fset.set FA.
  Hypothesis sort2 : (Index.containers S2) -> Fset.set FA.
  Hypothesis sup1 : o1 -> Fset.set FA.
  Hypothesis sup2 : o2 -> Fset.set FA.
  Hypothesis sup : o -> Fset.set FA.
  Hypothesis proj1_ : o -> o1.
  Hypothesis proj2_ : o -> o2.
  Hypothesis sa1 sa2: Fset.set FA.
  Hypothesis sup_eq : forall t1 t2, Oeset.compare O t1 t2 = Eq -> sup t1 =S= sup t2.
  Hypothesis sup_build : forall t1 t2, sup (build_ t1 t2) =S= (sup1 t1 unionS sup2 t2).
  Hypothesis sup_proj1 : forall t, sup1 (proj1_ t) =S= sa1.
  Hypothesis sup_proj2 : forall t, sup2 (proj2_ t) =S= sa2.
  Hypothesis build_proj :
    forall t, sup t =S= (sa1 unionS sa2) -> Oeset.compare O t (build_ (proj1_ t) (proj2_ t)) = Eq.
  Hypothesis build_eq :
    forall t1 u1 t2 u2, Oeset.compare O1 t1 u1 = Eq -> Oeset.compare O2 t2 u2 = Eq ->
                        Oeset.compare O (build_ t1 t2) (build_ u1 u2) = Eq.
  Hypothesis build_split_eq_1 : forall t1 u1 t2 u2,
      Oeset.compare O (build_ t1 t2) (build_ u1 u2) = Eq ->
      sup1 t1 =S= sup1 u1 -> sup2 t2 =S= sup2 u2 ->
      Oeset.compare O1 t1 u1 = Eq.
  Hypothesis build_split_eq_2 : forall t1 u1 t2 u2,
      Oeset.compare O (build_ t1 t2) (build_ u1 u2) = Eq ->
      sup1 t1 =S= sa1 -> sup1 t1 =S= sup1 u1 -> sup2 t2 =S= sa2 -> sup2 t2 =S= sup2 u2 ->
      Oeset.compare O2 t2 u2 = Eq.

  Notation IJ :=
    (IndexJoin.build _ _ _ _ O1 O2 OP O projS1 projS1_eq S1 S2 build_ build_eq_1 build_eq_2).

Lemma IndexJoin_is_a_filter_join_op :
  is_a_filter_join_op O1 O2 O
      (Cursor.materialize S1) (Index.c1 S2)
      (Cursor.materialize IJ)
      _ sup1 sup2 sup proj1_ proj2_
      (fun x => Index.P S2 (projS1 (proj1_ x)) (Index.proj S2 (proj2_ x)))
      sa1 sa2
      (fun c1 c2 => IndexJoin.mk_cursor
                      _ _ _ _ O1 O2 OP S1 S2 nil
                      c1 (Cursor.empty_cursor _) c2).
    Proof.
      intros c1 c2 H1 H2.
      assert (K1 : forall t1, In t1 (Cursor.collection c1) -> sup1 t1 =S= sa1).
      {
        intros t1 Ht1; apply H1.
        rewrite (Oeset.nb_occ_eq_2 _ _ _ _ (Cursor.materialize_collection _ _)).
        rewrite <- Oeset.nb_occ_diff_0_pos; apply Oeset.mem_nb_occ.
        apply Oeset.in_mem_bool; assumption.
      }
      assert (K2 : forall t2, In t2 (Index.c1 S2 c2) -> sup2 t2 =S= sa2).
      {
        intros t2 Ht2; apply H2.
        rewrite <- Oeset.nb_occ_diff_0_pos; apply Oeset.mem_nb_occ.
        apply Oeset.in_mem_bool; assumption.
      }
      split.
      - intros t Ht.
        rewrite (Oeset.nb_occ_eq_2 _ _ _ _ (Cursor.materialize_collection _ _)) in Ht.
        simpl in Ht; unfold IndexJoin.collection in Ht; simpl in Ht.
        rewrite IndexJoin.cross2_no_fold in Ht.
        rewrite <- Oeset.nb_occ_diff_0_pos in Ht.
        assert (Kt := Oeset.nb_occ_mem _ _ _ Ht).
        rewrite Oeset.mem_bool_true_iff in Kt; destruct Kt as [t' [Kt Ht']].
        rewrite  (Fset.equal_eq_1 _ _ _ _ (sup_eq _ _ Kt)).
        rewrite in_flat_map in Ht'.
        destruct Ht' as [t1 [Ht1 Ht']].
        rewrite in_map_iff in Ht'.
        destruct Ht' as [[b _t'] [_H Ht']].
        simpl in _H; subst _t'. rewrite filter_In in Ht'. destruct Ht' as [Ht' Hb]; subst b.
        rewrite in_map_iff in Ht'. destruct Ht' as [t2 [Ht' Kt']].
        injection Ht'; clear Ht'; intros Hb Ht'; subst t'.
        rewrite (Fset.equal_eq_1 _ _ _ _ (sup_build _ _)).
        apply Fset.union_eq; [apply K1 | apply K2]; trivial.
      - intros t Ht.
        rewrite 2 (Oeset.nb_occ_eq_2 _ _ _ _ (Cursor.materialize_collection _ _)); simpl.
        unfold IndexJoin.collection; simpl.
        rewrite IndexJoin.cross2_no_fold.
        unfold IndexJoin.S2P, IndexJoin.projS2.
        assert (Kt : Oeset.compare O t (build_ (proj1_ t) (proj2_ t)) = Eq).
        {
          apply build_proj; assumption.
        }
        set (l1 := (Cursor.collection c1)) in *; clearbody l1.
        set (l2 := Index.c1 S2 c2) in *; clearbody l2; clear H2.
        revert l2 K2; induction l1 as [ | t1 l1]; intros l2 K2; simpl; trivial.
        rewrite Oeset.nb_occ_app, IHl1; trivial; [ | intros; apply K1; right]; trivial.
        assert (Kt1 := K1 _ (or_introl _ (refl_equal _))).
        assert (K1' : forall t1, In t1 l1 -> sup1 t1 =S= sa1).
        {
          intros; apply K1; right; assumption.
        }
        rewrite !Nmult_plus_distr_r; apply f_equal2; [ | apply refl_equal].
        clear l1 K1 K1' IHl1; induction l2 as [ | t2 l2]; simpl;
          [rewrite N.mul_0_r, N.mul_0_l; trivial | ].
        case_eq (Index.P S2 (projS1 t1) (Index.proj S2 t2)); intro H; simpl; rewrite IHl2.
        + case_eq (Oeset.compare O t (build_ t1 t2)); intro Jt.
          * rewrite (Oeset.compare_eq_1 _ _ _ _ Kt) in Jt.
            {
              rewrite (build_split_eq_1 _ _ _ _ Jt), (build_split_eq_2 _ _ _ _ Jt).
              - replace (Index.P S2 (projS1 (proj1_ t)) (Index.proj S2 (proj2_ t))) with true.
                + rewrite 2 Nmult_1_l, 2 Nmult_1_r; apply refl_equal.
                + rewrite <- H.
                  apply trans_eq with (Index.P S2 (projS1 t1) (Index.proj S2 (proj2_ t))).
                  * apply sym_eq; apply Index.P_eq_2; apply Index.proj_eq.
                    {
                      apply (build_split_eq_2 _ _ _ _ Jt (sup_proj1 _)).
                      - rewrite (Fset.equal_eq_1 _ _ _ _ (sup_proj1 _)),
                          (Fset.equal_eq_2 _ _ _ _ Kt1).
                        apply Fset.equal_refl.
                      - apply sup_proj2.
                      - rewrite (Fset.equal_eq_1 _ _ _ _ (sup_proj2 _)).
                        rewrite (Fset.equal_eq_2 _ _ _ _ (K2 _ (or_introl _ (refl_equal _)))).
                        apply Fset.equal_refl.
                    }
                  * apply sym_eq; apply Index.P_eq_1; apply projS1_eq.
                    {
                      apply (build_split_eq_1 _ _ _ _ Jt).
                      - rewrite (Fset.equal_eq_1 _ _ _ _ (sup_proj1 _)),
                        (Fset.equal_eq_2 _ _ _ _ Kt1).
                        apply Fset.equal_refl.
                      - rewrite (Fset.equal_eq_1 _ _ _ _ (sup_proj2 _)).
                        rewrite (Fset.equal_eq_2 _ _ _ _ (K2 _ (or_introl _ (refl_equal _)))).
                        apply Fset.equal_refl.
                    }
              - rewrite (Fset.equal_eq_1 _ _ _ _ (sup_proj1 _)); apply Fset.equal_refl.
              - rewrite (Fset.equal_eq_1 _ _ _ _ (sup_proj1 _)),
                  (Fset.equal_eq_2 _ _ _ _ Kt1).
                apply Fset.equal_refl.
              - rewrite (Fset.equal_eq_1 _ _ _ _ (sup_proj2 _)).
                  apply Fset.equal_refl.
              - rewrite (Fset.equal_eq_1 _ _ _ _ (sup_proj2 _)).
                rewrite (Fset.equal_eq_2 _ _ _ _ (K2 _ (or_introl _ (refl_equal _)))).
                apply Fset.equal_refl.
              - rewrite (Fset.equal_eq_1 _ _ _ _ (sup_proj1 _)),
                  (Fset.equal_eq_2 _ _ _ _ Kt1).
                apply Fset.equal_refl.
              - rewrite (Fset.equal_eq_1 _ _ _ _ (sup_proj2 _)).
                rewrite (Fset.equal_eq_2 _ _ _ _ (K2 _ (or_introl _ (refl_equal _)))).
                apply Fset.equal_refl.
            }
          * rewrite N.add_0_l.
            {
              case_eq (Oeset.compare O1 (proj1_ t) t1); intro Jt1.
              - case_eq (Oeset.compare O2 (proj2_ t) t2); intro Jt2.
                + rewrite (Oeset.compare_eq_2 _ _ _ _ (build_eq _ _ _ _ Jt1 Jt2)) in Kt.
                  rewrite Kt in Jt; discriminate Jt.
                + rewrite N.add_0_l, Nmult_1_l; apply refl_equal.
                +  rewrite N.add_0_l, Nmult_1_l; apply refl_equal.
              - rewrite 2 N.mul_0_l; apply refl_equal.
              - rewrite 2 N.mul_0_l; apply refl_equal.
            }
          * rewrite N.add_0_l.
            {
              case_eq (Oeset.compare O1 (proj1_ t) t1); intro Jt1.
              - case_eq (Oeset.compare O2 (proj2_ t) t2); intro Jt2.
                + rewrite (Oeset.compare_eq_2 _ _ _ _ (build_eq _ _ _ _ Jt1 Jt2)) in Kt.
                  rewrite Kt in Jt; discriminate Jt.
                + rewrite N.add_0_l, Nmult_1_l; apply refl_equal.
                +  rewrite N.add_0_l, Nmult_1_l; apply refl_equal.
              - rewrite 2 N.mul_0_l; apply refl_equal.
              - rewrite 2 N.mul_0_l; apply refl_equal.
            }
        + intros; apply K2; right; assumption.
        + case_eq (Oeset.compare O1 (proj1_ t) t1); intro Jt1.
          *
            {
              case_eq (Oeset.compare O2 (proj2_ t) t2); intro Jt2.
              - assert (Hb : Index.P S2 (projS1 (proj1_ t)) (Index.proj S2 (proj2_ t)) = false).
                {
                  rewrite <- H.
                  apply trans_eq with (Index.P S2 (projS1 t1) (Index.proj S2 (proj2_ t))).
                  - apply Index.P_eq_1; apply projS1_eq; assumption.
                  - apply Index.P_eq_2; apply Index.proj_eq; assumption.
                }
                rewrite Hb, 2 N.mul_0_r; apply refl_equal.
              - rewrite N.add_0_l; trivial.
              - rewrite N.add_0_l; trivial.
            }
          * rewrite 2 N.mul_0_l; apply refl_equal.
          * rewrite 2 N.mul_0_l; apply refl_equal.
        + intros; apply K2; right; assumption.
Qed.

End IndexJoin_is_a_filter_join_op.

(* Group is grouping *)

Require Import Group.

Section Group_is_a_grouping_op.

  Variable elt : Type.
  Variable Elt : Oeset.Rcd elt.
  Variable C : Cursor.Rcd Elt.

  Variable key : Type.
  Variable Key : Oeset.Rcd key.

  Variable proj : elt -> key.
  Variable theta : list elt -> bool.
  Variable build : list elt -> elt.

  Hypothesis proj_eq : forall e1 e2,
      Oeset.compare Elt e1 e2 = Eq ->
      Oeset.compare Key (proj e1) (proj e2) = Eq.
  Hypothesis build_eq : forall l1 l2,
    comparelA (Oeset.compare Elt) l1 l2 = Eq ->
    Oeset.compare Elt (build l1) (build l2) = Eq.
  Hypothesis theta_eq : forall l1 l2,
    comparelA (Oeset.compare Elt) l1 l2 = Eq -> theta l1 = theta l2.

Notation assoc := (OrderedSet.group Key proj).

  Lemma Group_is_a_grouping_op :
    is_a_grouping_op
      Elt
      (Cursor.materialize (SeqScan.SeqScan.build Elt))
      (fun proj c => List.map snd (List.fold_left (fun acc e => assoc e acc) (Cursor.materialize C c) nil))
      proj
      theta
      build
      (group C Key proj theta build).
  Proof.
    intros q t.
    assert (Hg := Cursor.materialize_collection (SeqScan.SeqScan.build Elt) (group C Key proj theta build q)).
    assert (Hq := Cursor.materialize_collection C q).
    rewrite (Oeset.nb_occ_eq_2 _ t _ _ Hg).
    rewrite (Oeset.nb_occ_eq_2 _ t _ _ (group_collection _ _ _ _ _ proj_eq build_eq theta_eq _)).
    apply permut_nb_occ, permut_refl_alt.
    apply (comparelA_map_eq_2 (OL Elt) _ _ _ _ build_eq).
    apply (comparelA_eq_filter _ _ _ _).
    - intros a1 a2 _; apply theta_eq.
    - apply (comparelA_map_eq_2 (OKL Elt Key)).
      + intros [k1 l1] [k2 l2]; simpl.
        now case (Oeset.compare Key k1 k2); try discriminate.
      + apply (comparelA_fold_left_eq (mk_oelists (OKL Elt Key)) Elt).
        * now intros; apply assoc_eq.
        * now apply (Oeset.compare_eq_sym (OL Elt)).
  Qed.

End Group_is_a_grouping_op.


(*************************************************************)
(*   Copyright Dominique Larchey-Wendling [*]                 *)
(*                                                            *)
(*                             [*] Affiliation LORIA -- CNRS  *)
(*************************************************************)
(*      This file is distributed under the terms of the       *)
(*         CeCILL v2 FREE SOFTWARE LICENSE AGREEMENT          *)
(*************************************************************)

Require Import List Arith Bool Lia Eqdep_dec.

From Undecidability.Shared.Libs.DLW.Utils
  Require Import utils_tac utils_list utils_nat finite.

From Undecidability.Shared.Libs.DLW.Vec
  Require Import pos vec.

From Undecidability.TRAKHTENBROT
  Require Import notations utils fol_ops fo_sig fo_terms fo_logic fo_sat decidable.

Set Default Proof Using "Type".

Set Implicit Arguments.

(* * Decidability results for FSAT *)

Local Notation ø := vec_nil.
Local Infix "∊" := In (at level 70, no associativity).
Local Infix "⊑" := incl (at level 70, no associativity).

Section FSAT_ext.

  Variable (Σ : fo_signature) (X : Type) (P Q : X -> Prop)
           (HPQ : forall x, P x <-> Q x)
           (HP : forall x (H1 H2 : P x), H1 = H2)
           (HQ : forall x (H1 H2 : Q x), H1 = H2)
           (M : fo_model Σ (sig P))
           (Mdec : fo_model_dec M)
           (Mfin : finite_t (sig P)).

  Let f : sig P -> sig Q.
  Proof.
    intros (x & Hx); exists x; apply HPQ; auto.
  Defined.

  Let g : sig Q -> sig P.
  Proof.
    intros (x & Hx); exists x; apply HPQ; auto.
  Defined.

  Let Hfg : forall x, f (g x) = x.
  Proof.
    intros (x & Hx).
    unfold f, g; simpl; f_equal; apply HQ.
  Qed.

  Let Hgf : forall x, g (f x) = x.
  Proof.
    intros (x & Hx).
    unfold f, g; simpl; f_equal; apply HP.
  Qed.

  Let sigQ_fin : finite_t (sig Q).
  Proof.
    revert Mfin.
    apply finite_t_map with (f := f).
    intros y; exists (g y); auto.
  Qed.

  Let M' : fo_model Σ (sig Q).
  Proof.
    split.
    + intros s v.
      apply f, (fom_syms M s), (vec_map g v).
    + intros r v.
      apply (fom_rels M r (vec_map g v)).
  Defined.

  Let M'_dec : fo_model_dec M'.
  Proof.
    intros r v; simpl; apply Mdec.
  Qed.

  Variable (A : fol_form Σ).

  Let ls := fol_syms A.
  Let lr := fol_rels A.

  Let p : @fo_projection Σ ls lr _ M _ M'.
  Proof.
    exists f g; auto.
    + intros s v _; simpl; do 2 f_equal.
      apply vec_pos_ext; intro; rew vec.
    + intros r v _; simpl.
      fol equiv rel.
      apply vec_pos_ext; intro; rew vec.
  Defined.

  Variables (phi : nat -> sig P) (HA : fol_sem M phi A).

  Local Theorem fo_form_fin_dec_SAT_in_ext :
          @fo_form_fin_dec_SAT_in Σ A (sig (fun x => Q x)).
  Proof using HA HP HPQ sigQ_fin Mfin Mdec M'_dec Hfg HQ.
    exists M', sigQ_fin, M'_dec, (fun n => f (phi n)).
    revert HA.
    apply fo_model_projection with (p := p).
    + intros; simpl; auto.
    + apply incl_refl.
    + apply incl_refl.
  Qed.

End FSAT_ext.

Theorem FSAT_in_ext Σ A X (P Q : X -> bool) :
           (forall x, P x = true <-> Q x = true)
       -> @fo_form_fin_dec_SAT_in Σ A (sig (fun x => P x = true))
      <-> @fo_form_fin_dec_SAT_in Σ A (sig (fun x => Q x = true)).
Proof.
  intros H; split.
  + intros (M & H1 & H2 & phi & H3).
    apply fo_form_fin_dec_SAT_in_ext with (M := M) (phi := phi); auto;
      intros; apply eq_bool_pirr.
  + intros (M & H1 & H2 & phi & H3).
    apply fo_form_fin_dec_SAT_in_ext with (M := M) (phi := phi); auto.
    2-3: intros; apply eq_bool_pirr.
    intros; rewrite H; tauto.
Qed.

Section enum_models.

  Variables (Σ : fo_signature)
            (HΣ1 : discrete (syms Σ))
            (HΣ2 : discrete (rels Σ))
            (X : Type)
            (HX1 : finite_t X)
            (HX2 : discrete X) (x : X)
            (ls : list (syms Σ))
            (lr : list (rels Σ))
            (ln : list nat).

  Let funs := (forall s, vec X (ar_syms Σ s) -> X).

  Let Rs : funs -> funs -> Prop.
  Proof.
    intros s1 s2.
    exact ( (forall s, In s ls -> forall v, s1 s v = s2 s v) ).
  Defined.

  Let finite_t_funs : finite_t_upto funs Rs.
  Proof. apply finite_t_model; auto. Qed.

  Let rels := { r : forall s, vec X (ar_rels Σ s) -> Prop & forall s v, decidable (r s v) }.

  Let Rr : rels -> rels -> Prop.
  Proof.
   intros (r1 & ?) (r2 & ?).
   exact ( (forall r, In r lr -> forall v, @r1 r v <-> r2 r v) ).
  Defined.

  Hint Resolve finite_t_bool : core.

  Let bool_prop (f : forall r, vec X (ar_rels Σ r) -> bool) : rels.
  Proof.
    exists (fun r v => f r v = true).
    intros; apply bool_dec.
  Defined.

  Let finite_t_rels : finite_t_upto rels Rr.
  Proof.
    destruct finite_t_model with (ar := ar_rels Σ) (X := X) (Y := bool) (ls := lr)
      as (l & Hl) ; auto.
    { exact true. }
    exists (map bool_prop l).
    intros (f & Hf).
    set (g := fun r v => if Hf r v then true else false).
    destruct (Hl g) as (g' & H1 & H2).
    exists (bool_prop g'); split.
    + apply in_map_iff; exists g'; auto.
    + simpl; intros r Hr v.
      rewrite <- H2; auto.
      unfold g.
      destruct (Hf r v); split; auto; discriminate.
  Qed.

  Let model := { M : fo_model Σ X &
               { _ : nat -> X & fo_model_dec M } }.

  (* Equivalence of FO models over type X up-to the symbols in ls lr
     and the variables in ln,
     ie same extensional interpretation of function symbols and
     equivalent interpretation of relations symbols *)

  Local Definition FO_model_equiv : model -> model -> Prop.
  Proof using ls lr ln.
    intros ((s1,r1) & rho1 & H1 ) ((s2,r2) & rho2 & H2).
    exact (  (forall s, s ∊ ls -> forall v, s1 s v = s2 s v)
          /\ (forall r, r ∊ lr -> forall v, @r1 r v <-> r2 r v)
          /\ (forall n, n ∊ ln -> rho1 n = rho2 n) ).
  Defined.

  Let combine : (funs * rels * (nat -> X)) -> model.
  Proof.
    intros ((f,(g & Hg)),rho).
    exists {| fom_syms := f; fom_rels := g |}, rho; auto.
  Defined.

  (* There are finitely many models upto equivalence *)

  Local Theorem finite_t_model_upto : finite_t_upto _ FO_model_equiv.
  Proof using finite_t_rels finite_t_funs HΣ2 HΣ1 HX2 HX1.
    destruct finite_t_funs as (lf & H1).
    destruct finite_t_rels as (lg & H2).
    destruct finite_t_valuations with X ln
      as (lrho & H3); auto.
    exists (map combine (list_prod (list_prod lf lg) lrho)).
    intros ((f,g) & rho & Hg).
    destruct (H1 f) as (f' & G1 & G2).
    destruct (H2 (existT _ g Hg)) as ((g' & Hg') & G3 & G4).
    destruct (H3 rho) as (phi & G5 & G6).
    exists (existT _ {| fom_syms := f'; fom_rels := g' |} (existT _ phi Hg')); simpl; split.
    + apply in_map_iff.
      exists ((f',existT _ g' Hg'),phi); split; auto.
      apply list_prod_spec; split; auto.
      apply list_prod_spec; simpl; auto.
    + split; auto.
  Qed.

  Local Definition FO_sem : model -> fol_form Σ -> Prop.
  Proof.
    intros (M & rho & _) A.
    exact (fol_sem M rho A).
  Defined.

  Theorem FO_model_equiv_spec (M1 M2 : model) A :
             FO_model_equiv M1 M2
          -> fol_vars A ⊑ ln
          -> fol_syms A ⊑ ls
          -> fol_rels A ⊑ lr
          -> FO_sem M1 A <-> FO_sem M2 A.
  Proof.
    intros H1 H2 H3 H4.
    destruct M1 as ((s1&r1) & rho1 & G1).
    destruct M2 as ((s2&r2) & rho2 & G2).
    simpl in H1 |- *.
    apply fo_model_projection' with (i := fun x => x) (j := fun x => x) (ls := ls) (lr := lr); auto.
    + intros s v Hs.
      replace (vec_map (fun x => x) v) with v; simpl; auto.
      * apply H1; auto.
      * apply vec_pos_ext; intro; rew vec.
    + intros r v Hr.
      replace (vec_map (fun x => x) v) with v.
      * apply H1; auto.
      * apply vec_pos_ext; intro; rew vec.
    + intros n Hn; apply H1; auto.
  Qed.

  Theorem FSAT_FO_sem_eq A : @fo_form_fin_dec_SAT_in Σ A X <-> exists M, FO_sem M A.
  Proof using HX1.
    split.
    + intros (M & H1 & H2 & rho & H3).
      exists (existT _ M (existT _ rho H2)); simpl; auto.
    + intros ((M & rho & H1) & H2).
      exists M, HX1, H1, rho; auto.
  Qed.

End enum_models.

Section FSAT_in_dec.

  (* The main theorem here:

      - Given a discrete FO signature Σ
      - Given a finite and discrete base type X

      Having a Σ-model over that base type X is a decidable property *)

  Variables (Σ : fo_signature) (HΣ1 : discrete (syms Σ)) (HΣ2 : discrete (rels Σ))
            (X : Type) (HX1 : finite_t X) (HX2 : discrete X)
            (A : fol_form Σ).

  Theorem FSAT_in_dec : decidable (@fo_form_fin_dec_SAT_in Σ A X).
  Proof using HΣ2 HΣ1 HX2 HX1.
    destruct HX1 as ([ | x l ] & Hl).
    + right; intros (M & _ & _ & rho & _).
      apply (Hl (rho 0)).
    + clear l Hl.
      assert (H : decidable (exists M, @FO_sem _ X M A)).
      { destruct finite_t_model_upto
          with (X := X)
               (ls := fol_syms A)
               (lr := fol_rels A)
               (ln := fol_vars A)
          as (lM & HlM); auto.
        apply decidable_list_upto_ex
          with (l := lM)
               (R := FO_model_equiv (fol_syms A)
                                    (fol_rels A)
                                    (fol_vars A)); auto.
        * intros (M & rho & H); simpl; apply fol_sem_dec; auto.
        * intros ? ? ?; eapply FO_model_equiv_spec.
          2-4: apply incl_refl.
          trivial. }
      destruct H as [ H | H ]; [ left | right ].
      * revert H; apply FSAT_FO_sem_eq; auto.
      * contradict H; revert H; apply FSAT_FO_sem_eq; auto.
  Qed.

End FSAT_in_dec.

Section fo_form_fin_discr_dec_SAT_pos.

  (* Having a finite and discrete model is the same
      as having a model over type pos n for some n *)

  Variables (Σ : fo_signature) (A : fol_form Σ).

  Theorem fo_form_fin_discr_dec_SAT_pos :
     fo_form_fin_discr_dec_SAT A <-> exists n, fo_form_fin_dec_SAT_in A (pos n).
  Proof.
    split.
    2: intros (n & Hn); exists (pos n); exists; auto.
    intros (X & HX & M & Xf & H2 & phi & H3).
    destruct (finite_t_discrete_bij_t_pos Xf HX)
      as (n & i & j & Hji & Hij).
    set (M' := Mk_fo_model Σ (fun s v => i (fom_syms M s (vec_map j v)))
                             (fun r v => fom_rels M r (vec_map j v))).
    exists n, M'.
    exists. { apply finite_t_pos. }
    exists. { intros r v; apply H2. }
    exists (fun x => i (phi x)).
    cut (fol_sem M phi A <-> fol_sem M' (fun x : nat => i (phi x)) A); try tauto.
    clear H3; revert phi; induction A as [ | r v | b B HB C HC | [] B HB ]; intros phi.
    + simpl; tauto.
    + simpl; rewrite vec_map_map; apply fol_equiv_ext; f_equal.
      apply vec_pos_ext; intros p; simpl; rew vec.
      generalize (vec_pos v p); clear r v p.
      intros t; induction t as [ x | s v IH ].
      * simpl; rewrite Hji; auto.
      * rew fot; unfold M' at 1; simpl; rewrite Hji.
        f_equal; apply vec_pos_ext; intros p; rew vec.
    + apply fol_bin_sem_ext; auto.
    + simpl; split.
      * intros (x & Hx); exists (i x).
        apply HB in Hx.
        revert Hx; apply fol_sem_ext; intros []; simpl; auto.
      * intros (p & Hp); exists (j p).
        apply HB; revert Hp.
        apply fol_sem_ext; intros []; simpl; auto.
    + simpl; split.
      * intros H p; generalize (H (j p)); rewrite HB.
        apply fol_sem_ext; intros []; simpl; auto.
      * intros H x; generalize (H (i x)); rewrite HB.
        apply fol_sem_ext; intros []; simpl; auto.
  Qed.

End fo_form_fin_discr_dec_SAT_pos.


From Test Require Import tactic.

Section FOFProblem.

Variable Universe : Set.
Variable UniverseElement : Universe.

Variable triangle_ : Universe -> Universe -> Universe -> Prop.
Variable par_ : Universe -> Universe -> Universe -> Universe -> Prop.
Variable out_ : Universe -> Universe -> Universe -> Prop.
Variable oS_ : Universe -> Universe -> Universe -> Universe -> Prop.
Variable ltA_ : Universe -> Universe -> Universe -> Universe -> Universe -> Universe -> Prop.
Variable eT_ : Universe -> Universe -> Universe -> Universe -> Universe -> Universe -> Prop.
Variable congA_ : Universe -> Universe -> Universe -> Universe -> Universe -> Universe -> Prop.
Variable cong_ : Universe -> Universe -> Universe -> Universe -> Prop.
Variable col_ : Universe -> Universe -> Universe -> Prop.
Variable betS_ : Universe -> Universe -> Universe -> Prop.

Variable lemma_samesidesymmetric_1 : (forall A B P Q : Universe, (oS_ P Q A B -> (oS_ Q P A B /\ (oS_ P Q B A /\ oS_ Q P B A)))).
Variable lemma_samesideflip_2 : (forall A B P Q : Universe, (oS_ P Q A B -> oS_ P Q B A)).
Variable deftriangle_3 : (forall A B C : Universe, (triangle_ A B C -> ~(col_ A B C))).
Variable deftriangle2_4 : (forall A B C : Universe, (~(col_ A B C) -> triangle_ A B C)).
Variable lemma_NCdistinct_5 : (forall A B C : Universe, (~(col_ A B C) -> (A <> B /\ (B <> C /\ (A <> C /\ (B <> A /\ (C <> B /\ C <> A))))))).
Variable lemma_inequalitysymmetric_6 : (forall A B : Universe, (A <> B -> B <> A)).
Variable lemma_ray4_1_7 : (forall A B E : Universe, ((betS_ A E B /\ A <> B) -> out_ A B E)).
Variable lemma_ray4_2_8 : (forall A B E : Universe, ((E = B /\ A <> B) -> out_ A B E)).
Variable lemma_ray4_3_9 : (forall A B E : Universe, ((betS_ A B E /\ A <> B) -> out_ A B E)).
Variable lemma_crossbar2_10 : (forall A G H P S T : Universe, (exists X : Universe, ((ltA_ H G A H G P /\ (oS_ A P G H /\ (out_ G H S /\ out_ G P T))) -> (betS_ T X S /\ out_ G A X)))).
Variable proposition_39A_11 : (forall A B C D M : Universe, ((triangle_ A B C /\ (eT_ A B C D B C /\ (betS_ A M C /\ out_ B D M))) -> par_ A D B C)).
Variable axiom_ETsymmetric_12 : (forall A B C Ca Cb Cc : Universe, (eT_ A B C Ca Cb Cc -> eT_ Ca Cb Cc A B C)).
Variable lemma_parallelflip_13 : (forall A B C D : Universe, (par_ A B C D -> (par_ B A C D /\ (par_ A B D C /\ par_ B A D C)))).
Variable lemma_NCorder_14 : (forall A B C : Universe, (~(col_ A B C) -> (~(col_ B A C) /\ (~(col_ B C A) /\ (~(col_ C A B) /\ (~(col_ A C B) /\ ~(col_ C B A))))))).
Variable lemma_angletrichotomy2_15 : (forall A B C D E F : Universe, ((~(col_ A B C) /\ (~(col_ D E F) /\ (~(congA_ A B C D E F) /\ ~(ltA_ D E F A B C)))) -> ltA_ A B C D E F)).
Variable axiom_ETpermutation_16 : (forall A B C Ca Cb Cc : Universe, (eT_ A B C Ca Cb Cc -> (eT_ A B C Cb Cc Ca /\ (eT_ A B C Ca Cc Cb /\ (eT_ A B C Cb Ca Cc /\ (eT_ A B C Cc Cb Ca /\ eT_ A B C Cc Ca Cb)))))).
Variable lemma_equalanglessymmetric_17 : (forall A B C Xa Xb Xc : Universe, (congA_ A B C Xa Xb Xc -> congA_ Xa Xb Xc A B C)).
Variable cn_congruencereflexive_18 : (forall A B : Universe, cong_ A B A B).
Variable lemma_equalanglesflip_19 : (forall A B C D E F : Universe, (congA_ A B C D E F -> congA_ C B A F E D)).
Variable proposition_26A_20 : (forall A B C D E F : Universe, ((triangle_ A B C /\ (triangle_ D E F /\ (congA_ A B C D E F /\ (congA_ B C A E F D /\ cong_ B C E F)))) -> (cong_ A B D E /\ (cong_ A C D F /\ congA_ B A C E D F)))).
Variable proposition_07_21 : (forall A B C D : Universe, ((A <> B /\ (cong_ C A D A /\ (cong_ C B D B /\ oS_ C D A B))) -> C = D)).

Theorem proposition_39_22 : (forall A B C D : Universe, ((triangle_ A B C /\ (triangle_ D B C /\ (oS_ A D B C /\ (eT_ A B C D B C /\ A <> D)))) -> par_ A D B C)).
Proof.
  time tac.
Qed.

End FOFProblem.