Require Import rt.util.all.
Require Import rt.model.basic.task rt.model.basic.job rt.model.basic.schedule
               rt.model.basic.task_arrival rt.model.basic.platform rt.model.basic.response_time
               rt.model.basic.workload rt.model.basic.priority rt.model.basic.schedulability
               rt.model.basic.interference rt.model.basic.interference_edf.
Require Import rt.analysis.basic.workload_bound rt.analysis.basic.interference_bound.
From mathcomp Require Import ssreflect ssrbool eqtype ssrnat seq fintype bigop div path.

Module InterferenceBoundEDF.

  Import Job SporadicTaskset Schedule ScheduleOfSporadicTask Schedulability
         WorkloadBound ResponseTime Priority
         SporadicTaskArrival Interference InterferenceEDF.
  Export InterferenceBoundGeneric.

  (* In this section, we define Bertogna and Cirinei's EDF-specific
     interference bound. *)
  Section SpecificBoundDef.

    Context {sporadic_task: eqType}.
    Variable task_cost: sporadic_task -> time.
    Variable task_period: sporadic_task -> time.
    Variable task_deadline: sporadic_task -> time.

    (* Let tsk be the task to be analyzed. *)
    Variable tsk: sporadic_task.

    (* Consider the interference incurred by tsk in a window of length delta... *)
    Variable delta: time.

    (* ... due to a different task tsk_other, with response-time bound R_other. *)
    Variable tsk_other: sporadic_task.
    Variable R_other: time.

    (* Bertogna and Cirinei define the following bound for task interference
       under EDF scheduling. *)
    Definition edf_specific_interference_bound :=
      let d_tsk := task_deadline tsk in
      let e_other := task_cost tsk_other in
      let p_other := task_period tsk_other in
      let d_other := task_deadline tsk_other in
        (div_floor d_tsk p_other) * e_other +
        minn e_other ((d_tsk %% p_other) - (d_other - R_other)).

  End SpecificBoundDef.

  (* Next, we define the total interference bound for EDF, which combines the generic
     and the EDF-specific bounds. *)
  Section TotalInterferenceBoundEDF.

    Context {sporadic_task: eqType}.
    Variable task_cost: sporadic_task -> time.
    Variable task_period: sporadic_task -> time.
    Variable task_deadline: sporadic_task -> time.

    (* Let tsk be the task to be analyzed. *)
    Variable tsk: sporadic_task.

    Let task_with_response_time := (sporadic_task * time)%type.

    (* Assume a known response-time bound for each interfering task ... *)
    Variable R_prev: seq task_with_response_time.

    (* ... and an interval length delta. *)
    Variable delta: time.

    Section PerTask.

      Variable tsk_R: task_with_response_time.
      Let tsk_other := fst tsk_R.
      Let R_other := snd tsk_R.

      (* By combining Bertogna's interference bound for a work-conserving
         scheduler ... *)
      Let basic_interference_bound := interference_bound_generic task_cost task_period tsk delta tsk_R.

      (* ... with and EDF-specific interference bound, ... *)
      Let edf_specific_bound := edf_specific_interference_bound task_cost task_period task_deadline tsk tsk_other R_other.

      (* ... Bertogna and Cirinei define the following interference bound
         under EDF scheduling. *)
      Definition interference_bound_edf :=
        minn basic_interference_bound edf_specific_bound.

    End PerTask.

    Section AllTasks.

      Let interferes_with_tsk := jldp_can_interfere_with tsk.

      (* The total interference incurred by tsk is bounded by the sum
         of individual task interferences. *)
      Definition total_interference_bound_edf :=
        \sum_((tsk_other, R_other) <- R_prev | interferes_with_tsk tsk_other)
           interference_bound_edf (tsk_other, R_other).

    End AllTasks.

  End TotalInterferenceBoundEDF.

  (* In this section, we show that the EDF-specific interference bound is safe. *)
  Section ProofSpecificBound.

    Import Schedule Interference Platform SporadicTaskset.

    Context {sporadic_task: eqType}.
    Variable task_cost: sporadic_task -> time.
    Variable task_period: sporadic_task -> time.
    Variable task_deadline: sporadic_task -> time.

    Context {Job: eqType}.
    Variable job_cost: Job -> time.
    Variable job_deadline: Job -> time.
    Variable job_task: Job -> sporadic_task.

    (* Assume any job arrival sequence... *)
    Context {arr_seq: arrival_sequence Job}.

    (* ... in which jobs arrive sporadically and have valid parameters. *)
    Hypothesis H_sporadic_tasks:
      sporadic_task_model task_period arr_seq job_task.
    Hypothesis H_valid_job_parameters:
      forall (j: JobIn arr_seq),
        valid_sporadic_job task_cost task_deadline job_cost job_deadline job_task j.

    (* Consider any schedule such that...*)
    Variable num_cpus: nat.
    Variable sched: schedule num_cpus arr_seq.

    (* ...jobs do not execute before their arrival times nor longer
       than their execution costs. *)
    Hypothesis H_jobs_must_arrive_to_execute:
      jobs_must_arrive_to_execute sched.
    Hypothesis H_completed_jobs_dont_execute:
      completed_jobs_dont_execute job_cost sched.

    (* Also assume that jobs are sequential and that there exists at
       least one processor. *)
    Hypothesis H_sequential_jobs: sequential_jobs sched.
    Hypothesis H_at_least_one_cpu: num_cpus > 0.

    (* Consider a task set ts where all jobs come from the task set
       and tasks have valid parameters and constrained deadlines. *)
    Variable ts: taskset_of sporadic_task.
    Hypothesis all_jobs_from_taskset:
      forall (j: JobIn arr_seq), job_task j \in ts.
    Hypothesis H_valid_task_parameters:
      valid_sporadic_taskset task_cost task_period task_deadline ts.
    Hypothesis H_constrained_deadlines:
      forall tsk, tsk \in ts -> task_deadline tsk <= task_period tsk.

    Let no_deadline_is_missed_by_tsk (tsk: sporadic_task) :=
      task_misses_no_deadline job_cost job_deadline job_task sched tsk.
    Let response_time_bounded_by (tsk: sporadic_task) :=
      is_response_time_bound_of_task job_cost job_task tsk sched.

    (* Assume that the scheduler is a work-conserving EDF scheduler. *)
    Hypothesis H_work_conserving: work_conserving job_cost sched.
    Hypothesis H_edf_scheduler:
      enforces_JLDP_policy job_cost sched (EDF job_deadline).

    (* Let tsk_i be the task to be analyzed, ...*)
    Variable tsk_i: sporadic_task.
    Hypothesis H_tsk_i_in_task_set: tsk_i \in ts.

    (* ... and j_i one of its jobs. *)
    Variable j_i: JobIn arr_seq.
    Hypothesis H_job_of_tsk_i: job_task j_i = tsk_i.

    (* Let tsk_k denote any interfering task, ... *)
    Variable tsk_k: sporadic_task.
    Hypothesis H_tsk_k_in_task_set: tsk_k \in ts.

    (* ...and R_k its response-time bound. *)
    Variable R_k: time.
    Hypothesis H_R_k_le_deadline: R_k <= task_deadline tsk_k.

    (* Consider a time window of length delta <= D_i, starting with j_i's arrival time. *)
    Variable delta: time.
    Hypothesis H_delta_le_deadline: delta <= task_deadline tsk_i.

    (* Assume that the jobs of tsk_k satisfy the response-time bound before the end of the interval *)
    Hypothesis H_all_previous_jobs_completed_on_time :
      forall (j_k: JobIn arr_seq),
        job_task j_k = tsk_k ->
        job_arrival j_k + R_k < job_arrival j_i + delta ->
        completed job_cost sched j_k (job_arrival j_k + R_k).

    (* In this section, we prove that Bertogna and Cirinei's EDF interference bound
       indeed bounds the interference caused by task tsk_k in the interval [t1, t1 + delta). *)
    Section MainProof.

      (* Let's call x the task interference incurred by job j due to tsk_k. *)
      Let x :=
        task_interference job_cost job_task sched j_i tsk_k
                          (job_arrival j_i) (job_arrival j_i + delta).

      (* Also, recall the EDF-specific interference bound for EDF. *)
      Let interference_bound :=
        edf_specific_interference_bound task_cost task_period task_deadline tsk_i tsk_k R_k.

      (* Let's simplify the names a bit. *)
      Let t1 := job_arrival j_i.
      Let t2 := job_arrival j_i + delta.
      Let D_i := task_deadline tsk_i.
      Let D_k := task_deadline tsk_k.
      Let p_k := task_period tsk_k.

      Let n_k := div_floor D_i p_k.

      (* Let's give a simpler name to job interference. *)
      Let interference_caused_by := job_interference job_cost sched j_i.

      (* Identify the subset of jobs that actually cause interference *)
      Let interfering_jobs :=
        filter (fun (x: JobIn arr_seq) =>
                 (job_task x == tsk_k) && (interference_caused_by x t1 t2 != 0))
               (jobs_scheduled_between sched t1 t2).

      (* Now, consider the list of interfering jobs sorted by arrival time. *)
      Let earlier_arrival := fun (x y: JobIn arr_seq) => job_arrival x <= job_arrival y.
      Let sorted_jobs := (sort earlier_arrival interfering_jobs).

      (* Now we proceed with the proof. The first step consists in simplifying the sum corresponding to the workload. *)
      Section SimplifyJobSequence.

        (* Use the alternative definition of task interference, based on
           individual job interference. *)
        Lemma interference_bound_edf_use_another_definition :
          x <= \sum_(j <- jobs_scheduled_between sched t1 t2 | job_task j == tsk_k)
                interference_caused_by j t1 t2.
        Proof.
          apply interference_le_interference_joblist.
        Qed.

        (* Remove the elements that we don't care about from the sum *)
        Lemma interference_bound_edf_simpl_by_filtering_interfering_jobs :
          \sum_(j <- jobs_scheduled_between sched t1 t2 | job_task j == tsk_k)
             interference_caused_by j t1 t2 =
          \sum_(j <- interfering_jobs) interference_caused_by j t1 t2.
        Proof.
          unfold interfering_jobs; rewrite big_filter.
          rewrite big_mkcond; rewrite [\sum_(_ <- _ | _) _]big_mkcond /=.
          apply eq_bigr; intros i _; clear -i.
          destruct (job_task i == tsk_k); rewrite ?andTb ?andFb; last by done.
          destruct (interference_caused_by i t1 t2 != 0) eqn:DIFF; first by done.
          by apply negbT in DIFF; rewrite negbK in DIFF; apply/eqP.
        Qed.

        (* Then, we consider the sum over the sorted sequence of jobs. *)
        Lemma interference_bound_edf_simpl_by_sorting_interfering_jobs :
          \sum_(j <- interfering_jobs) interference_caused_by j t1 t2 =
           \sum_(j <- sorted_jobs) interference_caused_by j t1 t2.
        Proof.
          by rewrite (eq_big_perm sorted_jobs) /=; last by rewrite -(perm_sort earlier_arrival).
        Qed.

        (* Note that both sequences have the same set of elements. *)
        Lemma interference_bound_edf_job_in_same_sequence :
          forall j,
            (j \in interfering_jobs) = (j \in sorted_jobs).
        Proof.
          by apply perm_eq_mem; rewrite -(perm_sort earlier_arrival).
        Qed.

        (* Also recall that all jobs in the sorted sequence is an interfering job of tsk_k, ... *)
        Lemma interference_bound_edf_all_jobs_from_tsk_k :
          forall j,
            j \in sorted_jobs ->
            job_task j = tsk_k /\
            interference_caused_by j t1 t2 != 0 /\
            j \in jobs_scheduled_between sched t1 t2.
        Proof.
          intros j LT.
          rewrite -interference_bound_edf_job_in_same_sequence mem_filter in LT.
          by move: LT => /andP [/andP [/eqP JOBi SERVi] INi]; repeat split.
        Qed.

        (* ...and consecutive jobs are ordered by arrival. *)
        Lemma interference_bound_edf_jobs_ordered_by_arrival :
          forall i elem,
            i < (size sorted_jobs).-1 ->
            earlier_arrival (nth elem sorted_jobs i) (nth elem sorted_jobs i.+1).
        Proof.
          intros i elem LT.
          assert (SORT: sorted earlier_arrival sorted_jobs).
            by apply sort_sorted; unfold total, earlier_arrival; ins; apply leq_total.
          by destruct sorted_jobs; simpl in *; [by rewrite ltn0 in LT | by apply/pathP].
        Qed.

        (* Finally, for any job of task tsk_k, the interference is bounded by the task cost. *)
        Lemma interference_bound_edf_interference_le_task_cost :
          forall j,
            j \in interfering_jobs ->
            interference_caused_by j t1 t2 <= task_cost tsk_k.
        Proof.
          rename H_valid_job_parameters into PARAMS.
          intros j; rewrite mem_filter; move => /andP [/andP [/eqP JOBj _] _].
          specialize (PARAMS j); des.
          apply leq_trans with (n := service_during sched j t1 t2);
            first by apply job_interference_le_service.
          by apply cumulative_service_le_task_cost with (job_task0 := job_task)
                              (task_deadline0 := task_deadline) (job_cost0 := job_cost)
                                                        (job_deadline0 := job_deadline).
        Qed.

      End SimplifyJobSequence.

      (* Next, we show that if the number of jobs is no larger than n_k,
         the workload bound trivially holds. *)
      Section InterferenceFewJobs.

        Hypothesis H_few_jobs: size sorted_jobs <= n_k.

        Lemma interference_bound_edf_holds_for_at_most_n_k_jobs :
           \sum_(j <- sorted_jobs) interference_caused_by j t1 t2 <=
             interference_bound.
        Proof.
          rewrite -[\sum_(_ <- _ | _) _]addn0 leq_add //.
          apply leq_trans with (n := \sum_(x <- sorted_jobs) task_cost tsk_k);
            last by rewrite big_const_seq iter_addn addn0 mulnC leq_mul2r; apply/orP; right.
          {
            rewrite [\sum_(_ <- _) interference_caused_by _ _ _]big_seq_cond.
            rewrite [\sum_(_ <- _) task_cost _]big_seq_cond.
            apply leq_sum; intros i; move/andP => [INi _].
            rewrite -interference_bound_edf_job_in_same_sequence in INi.
            by apply interference_bound_edf_interference_le_task_cost.
          }
        Qed.

      End InterferenceFewJobs.

      (* Otherwise, assume that the number of jobs is larger than n_k >= 0. *)
      Section InterferenceManyJobs.

        Hypothesis H_many_jobs: n_k < size sorted_jobs.

        (* This trivially implies that there's at least one job. *)
        Lemma interference_bound_edf_at_least_one_job: size sorted_jobs > 0.
        Proof.
          by apply leq_ltn_trans with (n := n_k).
        Qed.

        (* Let j_fst be the first job, and a_fst its arrival time. *)
        Variable elem: JobIn arr_seq.
        Let j_fst := nth elem sorted_jobs 0.
        Let a_fst := job_arrival j_fst.

        (* In this section, we prove some basic lemmas about j_fst. *)
        Section FactsAboutFirstJob.

          (* The first job is an interfering job of task tsk_k. *)
          Lemma interference_bound_edf_j_fst_is_job_of_tsk_k :
            job_task j_fst = tsk_k /\
            interference_caused_by j_fst t1 t2 != 0 /\
            j_fst \in jobs_scheduled_between sched t1 t2.
          Proof.
            by apply interference_bound_edf_all_jobs_from_tsk_k, mem_nth,
                     interference_bound_edf_at_least_one_job.
          Qed.

          (* The deadline of j_fst is the deadline of tsk_k. *)
          Lemma interference_bound_edf_j_fst_deadline :
            job_deadline j_fst = task_deadline tsk_k.
          Proof.
            unfold valid_sporadic_job in *.
            rename H_valid_job_parameters into PARAMS.
            have FST := interference_bound_edf_j_fst_is_job_of_tsk_k.
            destruct FST as [FSTtask _].
            by specialize (PARAMS j_fst); des; rewrite PARAMS1 FSTtask.
          Qed.

          (* The deadline of j_i is the deadline of tsk_i. *)
          Lemma interference_bound_edf_j_i_deadline :
            job_deadline j_i = task_deadline tsk_i.
          Proof.
            unfold valid_sporadic_job in *.
            rename H_valid_job_parameters into PARAMS,
                   H_job_of_tsk_i into JOBtsk.
            by specialize (PARAMS j_i); des; rewrite PARAMS1 JOBtsk.
          Qed.

          (* If j_fst completes by its response-time bound, then t1 <= a_fst + R_k,
             where t1 is the beginning of the time window (arrival of j_i). *)
          Lemma interference_bound_edf_j_fst_completion_implies_rt_bound_inside_interval :
            completed job_cost sched j_fst (a_fst + R_k) ->
            t1 <= a_fst + R_k.
          Proof.
            intros RBOUND.
            rewrite leqNgt; apply/negP; unfold not; intro BUG.
            have FST := interference_bound_edf_j_fst_is_job_of_tsk_k.
            destruct FST as [_ [ FSTserv _]].
            move: FSTserv => /negP FSTserv; apply FSTserv.
            rewrite -leqn0; apply leq_trans with (n := service_during sched j_fst t1 t2);
              first by apply job_interference_le_service.
            rewrite leqn0; apply/eqP.
            by apply cumulative_service_after_job_rt_zero with (job_cost0 := job_cost) (R := R_k);
              try (by done); apply ltnW.
          Qed.

        End FactsAboutFirstJob.

        (* Now, let's prove the interference bound for the particular case of a single job.
           This case must be solved separately because the single job can simultaneously
           be carry-in and carry-out job, so its response time is not necessarily
           bounded by R_k (from the hypothesis H_all_previous_jobs_completed_on_time). *)
        Section InterferenceSingleJob.

          (* Assume that there's at least one job in the sorted list. *)
          Hypothesis H_only_one_job: size sorted_jobs = 1.

          (* Since there's only one job, we simplify the terms in the interference bound. *)
          Lemma interference_bound_edf_simpl_when_there's_one_job :
            D_i %% p_k - (D_k - R_k) = D_i - (D_k - R_k).
          Proof.
            rename H_many_jobs into NUM,
                   H_valid_task_parameters into TASK_PARAMS,
                   H_tsk_k_in_task_set into INk.
            unfold valid_sporadic_taskset, is_valid_sporadic_task,
                   interference_bound, edf_specific_interference_bound in *.
            rewrite H_only_one_job in NUM.
            rewrite ltnS leqn0 in NUM; move: NUM => /eqP EQnk.
            move: EQnk => /eqP EQnk; unfold n_k, div_floor in EQnk.
            rewrite -leqn0 leqNgt divn_gt0 in EQnk;
              last by specialize (TASK_PARAMS tsk_k INk); des.
            by rewrite -ltnNge in EQnk; rewrite modn_small //.
          Qed.

          (* Next, we show that if j_fst completes by its response-time bound R_k,
             then then interference bound holds. *)
          Section ResponseTimeOfSingleJobBounded.

            Hypothesis H_j_fst_completed_by_rt_bound :
              completed job_cost sched j_fst (a_fst + R_k).

            Lemma interference_bound_edf_holds_for_single_job_that_completes_on_time :
              job_interference job_cost sched j_i j_fst t1 t2 <= D_i - (D_k - R_k).
            Proof.
              rename H_j_fst_completed_by_rt_bound into RBOUND.
              have AFTERt1 :=
                interference_bound_edf_j_fst_completion_implies_rt_bound_inside_interval RBOUND.
              have FST := interference_bound_edf_j_fst_is_job_of_tsk_k.
              destruct FST as [_ [ LEdl _]].
              apply interference_under_edf_implies_shorter_deadlines with
                   (job_deadline0 := job_deadline) in LEdl; try (by done).
              destruct (D_k - R_k <= D_i) eqn:LEdk; last first.
              {
                apply negbT in LEdk; rewrite -ltnNge in LEdk.
                apply leq_trans with (n := 0); last by done.
                apply leq_trans with (n := job_interference job_cost sched j_i j_fst
                                                                        (a_fst + R_k) t2).
                {
                  apply extend_sum; last by apply leqnn.
                  rewrite -(leq_add2r D_i).
                  rewrite interference_bound_edf_j_fst_deadline
                          interference_bound_edf_j_i_deadline in LEdl.
                  apply leq_trans with (n := a_fst + D_k); last by done.
                  rewrite -addnA leq_add2l.
                  by apply ltnW; rewrite -ltn_subRL.
                }
                apply leq_trans with (n := service_during sched j_fst (a_fst + R_k) t2);
                  first by apply job_interference_le_service.
                unfold service_during; rewrite leqn0; apply/eqP.
                by apply cumulative_service_after_job_rt_zero with (job_cost0 := job_cost) (R := R_k);
                  try (by done); apply leqnn.
              }
              {
                rewrite -(leq_add2r (D_k - R_k)) subh1 // -addnBA // subnn addn0.
                assert (SUBST: D_k - R_k = \sum_(a_fst + R_k <= i < a_fst + D_k) 1).
                {
                  rewrite big_const_nat iter_addn mul1n addn0.
                  rewrite addnC -subnBA; last by apply leq_addr.
                  by rewrite addnC -addnBA // subnn addn0.
                }
                apply leq_trans with (n := job_interference job_cost sched j_i j_fst t1
                                                            (a_fst + D_k) + (D_k - R_k)).
                {
                  rewrite leq_add2r.
                  destruct (t2 <= a_fst + R_k) eqn:LEt2.
                  {
                    apply extend_sum; first by apply leqnn.
                    apply leq_trans with (n := a_fst + R_k); first by done.
                    by rewrite leq_add2l; apply H_R_k_le_deadline.
                  }
                  {
                    unfold job_interference.
                    apply negbT in LEt2; rewrite -ltnNge in LEt2.
                    rewrite -> big_cat_nat with (n := a_fst + R_k);
                      [simpl | by apply AFTERt1 | by apply ltnW].
                    apply leq_trans with (n := job_interference job_cost sched j_i j_fst t1
                                 (a_fst + R_k) + service_during sched j_fst (a_fst + R_k) t2).
                    {
                      rewrite leq_add2l.
                      by apply job_interference_le_service.
                    }
                    unfold service_during.
                    rewrite -> cumulative_service_after_job_rt_zero with
                      (job_cost0 := job_cost) (R := R_k); try (by done).
                    rewrite addn0; apply extend_sum; first by apply leqnn.
                    by rewrite leq_add2l; apply H_R_k_le_deadline.
                  }
                }

                unfold job_interference.
                rewrite -> big_cat_nat with (n := a_fst + R_k);
                  [simpl| by apply AFTERt1 | by rewrite leq_add2l; apply H_R_k_le_deadline].
                apply leq_trans with (n := job_interference job_cost sched j_i j_fst t1
                  (a_fst+R_k) + service_during sched j_fst (a_fst+R_k) (a_fst+D_k) + (D_k-R_k)).
                {
                  rewrite leq_add2r leq_add2l.
                  by apply job_interference_le_service.
                }
                unfold service_during.
                rewrite -> cumulative_service_after_job_rt_zero with
                  (job_cost0 := job_cost) (R:=R_k); try (by done).
                rewrite addn0.
                apply leq_trans with (n := (\sum_(t1 <= t < a_fst + R_k) 1) +
                                           \sum_(a_fst + R_k <= t < a_fst + D_k) 1).
                {
                  apply leq_add; last by rewrite SUBST.
                  rewrite big_const_nat iter_addn mul1n addn0.
                  rewrite -{1}[a_fst + R_k](addKn t1) -addnBA //.
                  by apply job_interference_le_delta.
                }
                rewrite -big_cat_nat;
                  [simpl | by apply AFTERt1 | by rewrite leq_add2l; apply H_R_k_le_deadline ].
                rewrite big_const_nat iter_addn mul1n addn0 leq_subLR.
                by unfold D_i, D_k, t1, a_fst; rewrite -interference_bound_edf_j_fst_deadline
                                                       -interference_bound_edf_j_i_deadline.
              }
            Qed.

          End ResponseTimeOfSingleJobBounded.

          (* Else, if j_fst did not complete by its response-time bound, then
             we need a separate proof. *)
          Section ResponseTimeOfSingleJobNotBounded.

            Hypothesis H_j_fst_not_complete_by_rt_bound :
              ~~ completed job_cost sched j_fst (a_fst + R_k).

            (* This trivially implies that a_fst + R_k lies after the end of the interval,
               otherwise j_fst would have completed by its response-time bound. *)
            Lemma interference_bound_edf_response_time_bound_of_j_fst_after_interval :
              job_arrival j_fst + R_k >= job_arrival j_i + delta.
            Proof.
              have FST := interference_bound_edf_j_fst_is_job_of_tsk_k.
              destruct FST as [FSTtask _].
              rewrite leqNgt; apply/negP; intro LT.
              move: H_j_fst_not_complete_by_rt_bound => /negP BUG; apply BUG.
              by apply H_all_previous_jobs_completed_on_time.
            Qed.

            (* If the slack is too big (D_i < D_k - R_k), j_fst causes no interference. *)
            Lemma interference_bound_edf_holds_for_single_job_with_big_slack :
              D_i < D_k - R_k ->
              interference_caused_by j_fst t1 t2 = 0.
            Proof.
              intro LTdk.
              rewrite ltn_subRL in LTdk.
              rewrite -(ltn_add2l a_fst) addnA in LTdk.
              apply leq_ltn_trans with (m := t1 + D_i) in LTdk; last first.
              {
                rewrite leq_add2r.
                apply leq_trans with (n := t1 + delta); first by apply leq_addr.
                by apply interference_bound_edf_response_time_bound_of_j_fst_after_interval.
              }
              apply/eqP; rewrite -[_ _ _ _ == 0]negbK; apply/negP; red; intro BUG.
              apply interference_under_edf_implies_shorter_deadlines with
                     (job_deadline0 := job_deadline) in BUG; try (by done).
              rewrite interference_bound_edf_j_fst_deadline
                      interference_bound_edf_j_i_deadline in BUG.
              by apply (leq_trans LTdk) in BUG; rewrite ltnn in BUG.
            Qed.

            (* Else, if the slack is small, j_fst causes interference for no longer than
               D_i - (D_k - R_k). *)
            Lemma interference_bound_edf_holds_for_single_job_with_small_slack :
              D_i >= D_k - R_k ->
              interference_caused_by j_fst t1 t2 <= D_i - (D_k - R_k).
            Proof.
              intro LEdk.
              have FST := interference_bound_edf_j_fst_is_job_of_tsk_k.
              destruct FST as [FSTtask [LEdl _]].
              have LTr := interference_bound_edf_response_time_bound_of_j_fst_after_interval.
              apply subh3; last by apply LEdk.
              apply leq_trans with (n := job_interference job_cost sched j_i j_fst t1
                                                          (job_arrival j_fst + R_k) + (D_k - R_k));
                first by rewrite leq_add2r; apply extend_sum; [by apply leqnn|].
              apply leq_trans with (n := \sum_(t1 <= t < a_fst + R_k) 1 +
                                         \sum_(a_fst + R_k <= t < a_fst + D_k)1).
              {
                apply leq_add.
                {
                  rewrite big_const_nat iter_addn mul1n addn0.
                  rewrite -{1}[job_arrival j_fst + R_k](addKn t1) -addnBA;
                    first by apply job_interference_le_delta.
                  by apply leq_trans with (n := t1 + delta); first by apply leq_addr.
                }
                rewrite big_const_nat iter_addn mul1n addn0 addnC.
                rewrite -subnBA; last by apply leq_addr.
                by rewrite addnC -addnBA // subnn addn0.
              }
              rewrite -big_cat_nat; simpl; last 2 first.
              {
                apply leq_trans with (n := t1 + delta); first by apply leq_addr.
                by apply interference_bound_edf_response_time_bound_of_j_fst_after_interval.
              }
              by rewrite leq_add2l; apply H_R_k_le_deadline.
              rewrite big_const_nat iter_addn mul1n addn0 leq_subLR.
              unfold D_i, D_k, t1, a_fst; rewrite -interference_bound_edf_j_fst_deadline
                                                  -interference_bound_edf_j_i_deadline.
              by apply interference_under_edf_implies_shorter_deadlines with
                (job_deadline0 := job_deadline) in LEdl.
            Qed.

          End ResponseTimeOfSingleJobNotBounded.

          (* By combining the results above, we prove that the interference caused by the single job
             is bounded by D_i - (D_k - R_k), ... *)
          Lemma interference_bound_edf_interference_of_j_fst_limited_by_slack :
            interference_caused_by j_fst t1 t2 <= D_i - (D_k - R_k).
          Proof.
            destruct (completed job_cost sched j_fst (a_fst + R_k)) eqn:COMP;
              first by apply interference_bound_edf_holds_for_single_job_that_completes_on_time.
            apply negbT in COMP.
            destruct (ltnP D_i (D_k - R_k)) as [LEdk | LTdk].
              by rewrite interference_bound_edf_holds_for_single_job_with_big_slack.
              by apply interference_bound_edf_holds_for_single_job_with_small_slack.
          Qed.

          (* ... and thus the interference bound holds. *)
          Lemma interference_bound_edf_holds_for_a_single_job :
            interference_caused_by j_fst t1 t2 <= interference_bound.
          Proof.
            have ONE := interference_bound_edf_simpl_when_there's_one_job.
            have SLACK := interference_bound_edf_interference_of_j_fst_limited_by_slack.
            rename H_many_jobs into NUM, H_only_one_job into SIZE.
            unfold interference_caused_by, interference_bound, edf_specific_interference_bound.
            fold D_i D_k p_k n_k.
            rewrite SIZE ltnS leqn0 in NUM; move: NUM => /eqP EQnk.
            rewrite EQnk mul0n add0n.
            rewrite leq_min; apply/andP; split.
            {
              apply interference_bound_edf_interference_le_task_cost.
              rewrite interference_bound_edf_job_in_same_sequence.
              by apply mem_nth; rewrite SIZE.
            }
            by rewrite ONE; apply SLACK.
          Qed.

        End InterferenceSingleJob.

        (* Next, consider the other case where there are at least two jobs:
           the first job j_fst, and the last job j_lst. *)
        Section InterferenceTwoOrMoreJobs.

          (* Assume there are at least two jobs. *)
          Variable num_mid_jobs: nat.
          Hypothesis H_at_least_two_jobs : size sorted_jobs = num_mid_jobs.+2.

          (* Let j_lst be the last job of the sequence and a_lst its arrival time. *)
          Let j_lst := nth elem sorted_jobs num_mid_jobs.+1.
          Let a_lst := job_arrival j_lst.

          (* In this section, we prove some basic lemmas about the first and last jobs. *)
          Section FactsAboutFirstAndLastJobs.

            (* The last job is an interfering job of task tsk_k. *)
            Lemma interference_bound_edf_j_lst_is_job_of_tsk_k :
              job_task j_lst = tsk_k /\
              interference_caused_by j_lst t1 t2 != 0 /\
              j_lst \in jobs_scheduled_between sched t1 t2.
            Proof.
              apply interference_bound_edf_all_jobs_from_tsk_k, mem_nth.
              by rewrite H_at_least_two_jobs.
            Qed.

            (* The deadline of j_lst is the deadline of tsk_k. *)
            Lemma interference_bound_edf_j_lst_deadline :
              job_deadline j_lst = task_deadline tsk_k.
            Proof.
              unfold valid_sporadic_job in *.
              rename H_valid_job_parameters into PARAMS.
              have LST := interference_bound_edf_j_lst_is_job_of_tsk_k.
              destruct LST as [LSTtask _].
              by specialize (PARAMS j_lst); des; rewrite PARAMS1 LSTtask.
            Qed.

            (* The first job arrives before the last job. *)
            Lemma interference_bound_edf_j_fst_before_j_lst :
              job_arrival j_fst <= job_arrival j_lst.
            Proof.
              rename H_at_least_two_jobs into SIZE.
              unfold j_fst, j_lst; rewrite -[num_mid_jobs.+1]add0n.
              apply prev_le_next; last by rewrite SIZE leqnn.
              by intros i LT; apply interference_bound_edf_jobs_ordered_by_arrival.
            Qed.

            (* The last job arrives before the end of the interval. *)
            Lemma interference_bound_edf_last_job_arrives_before_end_of_interval :
              job_arrival j_lst < t2.
            Proof.
              rewrite leqNgt; apply/negP; unfold not; intro LT2.
              exploit interference_bound_edf_all_jobs_from_tsk_k.
              {
                apply mem_nth; instantiate (1 := num_mid_jobs.+1).
                by rewrite -(ltn_add2r 1) addn1 H_at_least_two_jobs addn1.
              }
              instantiate (1 := elem); move => [LSTtsk [/eqP LSTserv LSTin]].
              apply LSTserv; apply/eqP; rewrite -leqn0.
              apply leq_trans with (n := service_during sched j_lst t1 t2);
                first by apply job_interference_le_service.
              rewrite leqn0; apply/eqP; unfold service_during.
              by apply cumulative_service_before_job_arrival_zero.
            Qed.

            (* Since there are multiple jobs, j_fst is far enough from the end of
               the interval that its response-time bound is valid
               (by the assumption H_all_previous_jobs_completed_on_time). *)
            Lemma interference_bound_edf_j_fst_completed_on_time :
              completed job_cost sched j_fst (a_fst + R_k).
            Proof.
              have FST := interference_bound_edf_j_fst_is_job_of_tsk_k; des.
              set j_snd := nth elem sorted_jobs 1.
              exploit interference_bound_edf_all_jobs_from_tsk_k.
              {
                by apply mem_nth; instantiate (1 := 1); rewrite H_at_least_two_jobs.
              }
              instantiate (1 := elem); move => [SNDtsk [/eqP SNDserv _]].
              apply H_all_previous_jobs_completed_on_time; try (by done).
              apply leq_ltn_trans with (n := job_arrival j_snd); last first.
              {
                rewrite ltnNge; apply/negP; red; intro BUG; apply SNDserv.
                apply/eqP; rewrite -leqn0; apply leq_trans with (n := service_during
                                                                          sched j_snd t1 t2);
                  first by apply job_interference_le_service.
                rewrite leqn0; apply/eqP.
                by apply cumulative_service_before_job_arrival_zero.
              }
              apply leq_trans with (n := a_fst + p_k).
              {
                by rewrite leq_add2l; apply leq_trans with (n := D_k);
                  [by apply H_R_k_le_deadline | by apply H_constrained_deadlines].
              }

              (* Since jobs are sporadic, we know that the first job arrives
                 at least p_k units before the second. *)
              unfold p_k; rewrite -FST.
              apply H_sporadic_tasks; [| by rewrite SNDtsk | ]; last first.
              {
                apply interference_bound_edf_jobs_ordered_by_arrival.
                by rewrite H_at_least_two_jobs.
              }
              red; move => /eqP BUG.
              by rewrite nth_uniq in BUG; rewrite ?SIZE //;
                [ by apply interference_bound_edf_at_least_one_job
                | by rewrite H_at_least_two_jobs
                | by rewrite sort_uniq; apply filter_uniq, undup_uniq].
            Qed.

          End FactsAboutFirstAndLastJobs.

          (* Next, we prove that the distance between the first and last jobs is at least
             num_mid_jobs + 1 periods. *)
          Lemma interference_bound_edf_many_periods_in_between :
            a_lst - a_fst >= num_mid_jobs.+1 * p_k.
          Proof.
            unfold a_fst, a_lst, j_fst, j_lst.
            assert (EQnk: num_mid_jobs.+1=(size sorted_jobs).-1).
              by rewrite H_at_least_two_jobs.
            rewrite EQnk telescoping_sum;
              last by ins; apply interference_bound_edf_jobs_ordered_by_arrival.
            rewrite -[_ * _ tsk_k]addn0 mulnC -iter_addn -{1}[_.-1]subn0 -big_const_nat.
            rewrite big_nat_cond [\sum_(0 <= i < _)(_-_)]big_nat_cond.
            apply leq_sum; intros i; rewrite andbT; move => /andP LT; des.

            (* To simplify, call the jobs 'cur' and 'next' *)
            set cur := nth elem sorted_jobs i.
            set next := nth elem sorted_jobs i.+1.

            (* Show that cur arrives earlier than next *)
            assert (ARRle: job_arrival cur <= job_arrival next).
              by unfold cur, next; apply interference_bound_edf_jobs_ordered_by_arrival.

            (* Show that both cur and next are in the arrival sequence *)
            assert (INnth: cur \in interfering_jobs /\ next \in interfering_jobs).
            {
              rewrite 2!interference_bound_edf_job_in_same_sequence; split.
                by apply mem_nth, (ltn_trans LT0); destruct sorted_jobs; ins.
                by apply mem_nth; destruct sorted_jobs; ins.
            }
            rewrite 2?mem_filter in INnth; des.

            (* Use the sporadic task model to conclude that cur and next are separated
               by at least (task_period tsk) units. Of course this only holds if cur != next.
               Since we don't know much about the list (except that it's sorted), we must
               also prove that it doesn't contain duplicates. *)
            assert (CUR_LE_NEXT: job_arrival cur + task_period (job_task cur) <= job_arrival next).
            {
              apply H_sporadic_tasks; last by ins.
              unfold cur, next, not; intro EQ; move: EQ => /eqP EQ.
              rewrite nth_uniq in EQ; first by move: EQ => /eqP EQ; intuition.
                by apply ltn_trans with (n := (size sorted_jobs).-1); destruct sorted_jobs; ins.
                by destruct sorted_jobs; ins.
                by rewrite sort_uniq -/interfering_jobs filter_uniq // undup_uniq.
                by rewrite INnth INnth0.
            }
            by rewrite subh3 // addnC /p_k -INnth.
          Qed.

          (* Using the lemma above, we prove that the ratio n_k is at least the number of
             middle jobs + 1, ... *)
          Lemma interference_bound_edf_n_k_covers_middle_jobs_plus_one :
            n_k >= num_mid_jobs.+1.
          Proof.
            have DIST := interference_bound_edf_many_periods_in_between.
            have AFTERt1 :=
                interference_bound_edf_j_fst_completion_implies_rt_bound_inside_interval
                interference_bound_edf_j_fst_completed_on_time.
            rename H_valid_task_parameters into TASK_PARAMS,
                   H_tsk_k_in_task_set into INk.
            unfold valid_sporadic_taskset, is_valid_sporadic_task,
                   interference_bound, edf_specific_interference_bound in *.
            rewrite leqNgt; apply/negP; unfold not; intro LTnk; unfold n_k in LTnk.
            rewrite ltn_divLR in LTnk; last by specialize (TASK_PARAMS tsk_k INk); des.
            apply (leq_trans LTnk) in DIST; rewrite ltn_subRL in DIST.
            rewrite -(ltn_add2r D_k) -addnA [D_i + _]addnC addnA in DIST.
            apply leq_ltn_trans with (m := job_arrival j_i + D_i) in DIST; last first.
            {
              rewrite leq_add2r; apply (leq_trans AFTERt1).
              by rewrite leq_add2l; apply H_R_k_le_deadline.
            }
            have LST := interference_bound_edf_j_lst_is_job_of_tsk_k.
            destruct LST as [_ [ LEdl _]].
            apply interference_under_edf_implies_shorter_deadlines with
                (job_deadline0 := job_deadline) in LEdl; try (by done).
            unfold D_i, D_k in DIST; rewrite interference_bound_edf_j_lst_deadline
                                             interference_bound_edf_j_i_deadline in LEdl.
            by rewrite ltnNge LEdl in DIST.
          Qed.

          (* ... which allows bounding the interference of the middle and last jobs
             using n_k multiplied by the cost. *)
          Lemma interference_bound_edf_holds_for_middle_and_last_jobs :
            interference_caused_by j_lst t1 t2 +
              \sum_(0 <= i < num_mid_jobs)
                interference_caused_by (nth elem sorted_jobs i.+1) t1 t2
            <= n_k * task_cost tsk_k.
          Proof.
            apply leq_trans with (n := num_mid_jobs.+1 * task_cost tsk_k); last first.
            {
              rewrite leq_mul2r; apply/orP; right.
              by apply interference_bound_edf_n_k_covers_middle_jobs_plus_one.
            }
            rewrite mulSn; apply leq_add.
            {
              apply interference_bound_edf_interference_le_task_cost.
              rewrite interference_bound_edf_job_in_same_sequence.
              by apply mem_nth; rewrite H_at_least_two_jobs.
            }
            {
              apply leq_trans with (n := \sum_(0 <= i < num_mid_jobs) task_cost tsk_k);
                last by rewrite big_const_nat iter_addn addn0 mulnC subn0.
              rewrite big_nat_cond [\sum_(0 <= i < num_mid_jobs) task_cost _]big_nat_cond.
              apply leq_sum; intros i; rewrite andbT; move => /andP LT; des.
              apply interference_bound_edf_interference_le_task_cost.
              rewrite interference_bound_edf_job_in_same_sequence.
              apply mem_nth; rewrite H_at_least_two_jobs.
              by rewrite ltnS; apply leq_trans with (n := num_mid_jobs).
            }
          Qed.

          (* Now, since n_k < sorted_jobs = num_mid_jobs + 2, it follows that
             n_k = num_mid_jobs + 1. *)
          Lemma interference_bound_edf_n_k_equals_num_mid_jobs_plus_one :
            n_k = num_mid_jobs.+1.
          Proof.
            have NK := interference_bound_edf_n_k_covers_middle_jobs_plus_one.
            rename H_many_jobs into NUM, H_at_least_two_jobs into SIZE.
            move: NK; rewrite leq_eqVlt orbC; move => /orP NK; des;
             [by rewrite SIZE ltnS leqNgt NK in NUM | by done].
          Qed.

          (* After proving the bounds of the middle and last jobs, we do the same for
             the first job. This requires a different proof in order to exploit the slack. *)
          Section InterferenceOfFirstJob.

            (* As required by the next lemma, in order to move (D_i %% p_k) to the left of
               the inequality (<=), we must show that it is no smaller than the slack. *)
            Lemma interference_bound_edf_remainder_ge_slack :
              D_k - R_k <= D_i %% p_k.
            Proof.
              have AFTERt1 :=
                interference_bound_edf_j_fst_completion_implies_rt_bound_inside_interval
                interference_bound_edf_j_fst_completed_on_time.
              have NK := interference_bound_edf_n_k_equals_num_mid_jobs_plus_one.
              have DIST := interference_bound_edf_many_periods_in_between.
              rewrite -NK in DIST.
              rewrite -subndiv_eq_mod leq_subLR.
              fold (div_floor D_i p_k) n_k.
              rewrite addnBA; last by apply leq_trunc_div.
              apply leq_trans with (n := R_k + D_i - (a_lst - a_fst)); last by apply leq_sub2l.
              rewrite subnBA; last by apply interference_bound_edf_j_fst_before_j_lst.
              rewrite -(leq_add2r a_lst) subh1; last first.
              {
                apply leq_trans with (n := t2);
                  [by apply ltnW, interference_bound_edf_last_job_arrives_before_end_of_interval|].
                rewrite addnC addnA.
                apply leq_trans with (n := t1 + D_i).
                  unfold t2; rewrite leq_add2l; apply H_delta_le_deadline.
                by rewrite leq_add2r; apply AFTERt1.
              }
              rewrite -addnBA // subnn addn0 [D_k + _]addnC.
              apply leq_trans with (n := t1 + D_i);
                last by rewrite -addnA [D_i + _]addnC addnA leq_add2r addnC AFTERt1.
              have LST := interference_bound_edf_j_lst_is_job_of_tsk_k.
              destruct LST as [_ [ LSTserv _]].
              unfold D_i, D_k, a_lst, t1; rewrite -interference_bound_edf_j_lst_deadline
                                                  -interference_bound_edf_j_i_deadline.
              by apply interference_under_edf_implies_shorter_deadlines with
                                (job_deadline0 := job_deadline) in LSTserv.
            Qed.

            (* To conclude that the interference bound holds, it suffices to show that
               this reordered inequality holds. *)
            Lemma interference_bound_edf_simpl_by_moving_to_left_side :
              interference_caused_by j_fst t1 t2 + (D_k - R_k) + D_i %/ p_k * p_k <= D_i ->
              interference_caused_by j_fst t1 t2 <= D_i %% p_k - (D_k - R_k).
            Proof.
              intro LE.
              apply subh3; last by apply interference_bound_edf_remainder_ge_slack.
              by rewrite -subndiv_eq_mod; apply subh3; last by apply leq_trunc_div.
            Qed.

            (* Next, we prove that interference caused by j_fst is bounded by the length
               of the interval [t1, a_fst + R_k), ... *)
            Lemma interference_bound_edf_interference_of_j_fst_bounded_by_response_time :
               interference_caused_by j_fst t1 t2 <= \sum_(t1 <= t < a_fst + R_k) 1.
            Proof.
              assert (AFTERt1: t1 <= a_fst + R_k).
              {
                apply interference_bound_edf_j_fst_completion_implies_rt_bound_inside_interval.
                by apply interference_bound_edf_j_fst_completed_on_time.
              }
              destruct (leqP t2 (a_fst + R_k)) as [LEt2 | GTt2].
              {
                apply leq_trans with (n := job_interference job_cost sched j_i j_fst t1
                                                                              (a_fst + R_k));
                  first by apply extend_sum; rewrite ?leqnn.
                simpl_sum_const; rewrite -{1}[_ + R_k](addKn t1) -addnBA //.
                by apply job_interference_le_delta.
              }
              {
                unfold interference_caused_by, job_interference.
                rewrite -> big_cat_nat with (n := a_fst + R_k);
                  [simpl | by apply AFTERt1 | by apply ltnW].
                rewrite -[\sum_(_ <= _ < _) 1]addn0; apply leq_add.
                {
                  simpl_sum_const; rewrite -{1}[_ + R_k](addKn t1) -addnBA //.
                  by apply job_interference_le_delta.
                }
                apply leq_trans with (n := service_during sched j_fst (a_fst + R_k) t2);
                  first by apply job_interference_le_service.
                rewrite leqn0; apply/eqP.
                apply cumulative_service_after_job_rt_zero with (job_cost0 := job_cost) (R := R_k);
                  [ by done | | by apply leqnn].
                by apply interference_bound_edf_j_fst_completed_on_time.
              }
            Qed.

            (* ..., which leads to the following bounds based on interval lengths. *)
            Lemma interference_bound_edf_bounding_interference_with_interval_lengths :
              interference_caused_by j_fst t1 t2 + (D_k - R_k) + D_i %/ p_k * p_k <=
              \sum_(t1 <= t < a_fst + R_k) 1
              + \sum_(a_fst + R_k <= t < a_fst + D_k) 1
              + \sum_(a_fst + D_k <= t < a_lst + D_k) 1.
            Proof.
              apply leq_trans with (n := \sum_(t1 <= t < a_fst + R_k) 1 + (D_k - R_k) +
                                                                       D_i %/ p_k * p_k).
              {
                rewrite 2!leq_add2r.
                apply interference_bound_edf_interference_of_j_fst_bounded_by_response_time.
              }
              apply leq_trans with (n := \sum_(t1 <= t < a_fst + R_k) 1 + (D_k - R_k) +
                                                                        (a_lst - a_fst)).
              {
                rewrite leq_add2l; fold (div_floor D_i p_k) n_k.
                rewrite interference_bound_edf_n_k_equals_num_mid_jobs_plus_one.
                by apply interference_bound_edf_many_periods_in_between.
              }
              apply leq_trans with (n := \sum_(t1 <= t < a_fst + R_k) 1 +
                  \sum_(a_fst + R_k <= t < a_fst + D_k) 1 + \sum_(a_fst + D_k <= t < a_lst + D_k) 1).
              {
                by rewrite -2!addnA leq_add2l; apply leq_add;
                rewrite big_const_nat iter_addn mul1n addn0;
                rewrite ?subnDl ?subnDr leqnn.
              }
              by apply leqnn.
            Qed.

            (* To conclude, we show that the concatenation of these interval lengths equals
               (a_lst + D_k) - 1, ... *)
            Lemma interference_bound_edf_simpl_by_concatenation_of_intervals :
              \sum_(t1 <= t < a_fst + R_k) 1
              + \sum_(a_fst + R_k <= t < a_fst + D_k) 1
              + \sum_(a_fst + D_k <= t < a_lst + D_k) 1 = (a_lst + D_k) - t1.
            Proof.
              assert (AFTERt1: t1 <= a_fst + R_k).
              {
                apply interference_bound_edf_j_fst_completion_implies_rt_bound_inside_interval.
                by apply interference_bound_edf_j_fst_completed_on_time.
              }
              rewrite -big_cat_nat;
                [simpl | by apply AFTERt1 | by rewrite leq_add2l; apply H_R_k_le_deadline].
              rewrite -big_cat_nat; simpl; last 2 first.
              {
                apply leq_trans with (n := a_fst + R_k); first by apply AFTERt1.
                by rewrite leq_add2l; apply H_R_k_le_deadline.
              }
              {
                rewrite leq_add2r; unfold a_fst, a_lst, j_fst, j_lst.
                rewrite -[num_mid_jobs.+1]add0n; apply prev_le_next;
                  last by rewrite add0n H_at_least_two_jobs ltnSn.
                by ins; apply interference_bound_edf_jobs_ordered_by_arrival.
              }
              by rewrite big_const_nat iter_addn mul1n addn0.
            Qed.

            (* ... which results in proving that (a_lst + D_k) - t1 <= D_i.
               This holds because high-priority jobs have earlier deadlines. Therefore,
               the interference caused by the first job is bounded by D_i %% p_k - (D_k - R_k). *)
            Lemma interference_bound_edf_interference_of_j_fst_limited_by_remainder_and_slack :
              interference_caused_by j_fst t1 t2 <= D_i %% p_k - (D_k - R_k).
            Proof.
              apply interference_bound_edf_simpl_by_moving_to_left_side.
              apply (leq_trans interference_bound_edf_bounding_interference_with_interval_lengths).
              rewrite interference_bound_edf_simpl_by_concatenation_of_intervals leq_subLR.
              have LST := interference_bound_edf_j_lst_is_job_of_tsk_k.
              destruct LST as [_ [ LSTserv _]].
              unfold D_i, D_k, a_lst, t1; rewrite -interference_bound_edf_j_lst_deadline
                                                  -interference_bound_edf_j_i_deadline.
              by apply interference_under_edf_implies_shorter_deadlines
                with (job_deadline0 := job_deadline) in LSTserv.
            Qed.

          End InterferenceOfFirstJob.

          (* Using the lemmas above we show that the interference bound works in the
             case of two or more jobs. *)
          Lemma interference_bound_edf_holds_for_multiple_jobs :
            \sum_(0 <= i < num_mid_jobs.+2)
              interference_caused_by (nth elem sorted_jobs i) t1 t2 <= interference_bound.
          Proof.
            (* Knowing that we have at least two elements, we take first and last out of the sum *)
            rewrite [nth]lock big_nat_recl // big_nat_recr // /= -lock.
            rewrite addnA addnC addnA.

            have NK := interference_bound_edf_n_k_equals_num_mid_jobs_plus_one.

            (* We use the lemmas we proved to show that the interference bound holds. *)
            unfold interference_bound, edf_specific_interference_bound.
            fold D_i D_k p_k n_k.
            rewrite addnC addnA; apply leq_add;
              first by rewrite addnC interference_bound_edf_holds_for_middle_and_last_jobs.
            rewrite leq_min; apply/andP; split.
            {
              apply interference_bound_edf_interference_le_task_cost.
              rewrite interference_bound_edf_job_in_same_sequence.
              by apply mem_nth; rewrite H_at_least_two_jobs.
            }
            by apply interference_bound_edf_interference_of_j_fst_limited_by_remainder_and_slack.
          Qed.

        End InterferenceTwoOrMoreJobs.

      End InterferenceManyJobs.

      Theorem interference_bound_edf_bounds_interference :
        x <= interference_bound.
      Proof.
        (* Use the definition of workload based on list of jobs. *)
        apply (leq_trans interference_bound_edf_use_another_definition).

        (* We only care about the jobs that cause interference. *)
        rewrite interference_bound_edf_simpl_by_filtering_interfering_jobs.

        (* Now we order the list by job arrival time. *)
        rewrite interference_bound_edf_simpl_by_sorting_interfering_jobs.

        (* Next, we show that the workload bound holds if n_k
           is no larger than the number of interferings jobs. *)
        destruct (size sorted_jobs <= n_k) eqn:NUM;
          first by apply interference_bound_edf_holds_for_at_most_n_k_jobs.
        apply negbT in NUM; rewrite -ltnNge in NUM.

        (* Find some dummy element to use in the nth function *)
        assert (EX: exists elem: JobIn arr_seq, True).
          destruct sorted_jobs as [| j]; [by rewrite ltn0 in NUM | by exists j].
        destruct EX as [elem _].

        (* Now we index the sum to access the first and last elements. *)
        rewrite (big_nth elem).

        (* First, we show that the bound holds for an empty list of jobs. *)
        destruct (size sorted_jobs) as [| n] eqn:SIZE;
          first by rewrite big_geq.

        (* Then, we show the same for a single job, or for multiple jobs. *)
        rewrite SIZE; destruct n as [| num_mid_jobs].
        {
          rewrite big_nat_recr // big_geq //.
          rewrite [nth]lock /= -lock add0n.
          by apply interference_bound_edf_holds_for_a_single_job; rewrite SIZE.
        }
        {
          by apply interference_bound_edf_holds_for_multiple_jobs; first by rewrite SIZE.
        }
      Qed.

    End MainProof.

  End ProofSpecificBound.

  (* As required by the proof of convergence of EDF RTA, we show that the
     EDF-specific bound is monotonically increasing with both the size
     of the interval and the value of the previous response-time bounds. *)
  Section MonotonicitySpecificBound.

    Context {sporadic_task: eqType}.
    Variable task_cost: sporadic_task -> time.
    Variable task_period: sporadic_task -> time.
    Variable task_deadline: sporadic_task -> time.

    Variable tsk tsk_other: sporadic_task.
    Hypothesis H_period_positive: task_period tsk_other > 0.

    Variable delta delta' R R': time.
    Hypothesis H_delta_monotonic: delta <= delta'.
    Hypothesis H_response_time_monotonic: R <= R'.
    Hypothesis H_cost_le_rt_bound: task_cost tsk_other <= R.

    Lemma interference_bound_edf_monotonic :
      interference_bound_edf task_cost task_period task_deadline tsk delta (tsk_other, R) <=
      interference_bound_edf task_cost task_period task_deadline tsk delta' (tsk_other, R').
    Proof.
      rename H_response_time_monotonic into LEr, H_delta_monotonic into LEx,
             H_cost_le_rt_bound into LEcost, H_period_positive into GEperiod.
      unfold interference_bound_edf, interference_bound_generic.
      rewrite leq_min; apply/andP; split.
      {
        rewrite leq_min; apply/andP; split.
        apply leq_trans with (n :=  (minn (W task_cost task_period (fst (tsk_other, R))
                           (snd (tsk_other, R)) delta) (delta - task_cost tsk + 1)));
          first by apply geq_minl.
        apply leq_trans with (n := W task_cost task_period (fst (tsk_other, R))
                                                   (snd (tsk_other, R)) delta);
          [by apply geq_minl | by apply W_monotonic].
        apply leq_trans with (n := minn (W task_cost task_period (fst (tsk_other, R)) (snd (tsk_other, R)) delta) (delta - task_cost tsk + 1));
          first by apply geq_minl.
        apply leq_trans with (n := delta - task_cost tsk + 1);
          first by apply geq_minr.
        by rewrite leq_add2r leq_sub2r.
      }
      {
        apply leq_trans with (n := edf_specific_interference_bound task_cost task_period
                                                          task_deadline tsk tsk_other R);
          first by apply geq_minr.
        unfold edf_specific_interference_bound; simpl.
        rewrite leq_add2l leq_min; apply/andP; split; first by apply geq_minl.
        apply leq_trans with (n := task_deadline tsk %% task_period tsk_other -
                                   (task_deadline tsk_other - R));
          [by apply geq_minr | by rewrite 2?leq_sub2l 2?leq_sub2r // leq_sub2l].
      }
    Qed.

  End MonotonicitySpecificBound.

End InterferenceBoundEDF.


Require Import PL.Imp.
Require Import Coq.Relations.Relation_Operators.
Require Import Coq.Relations.Relation_Definitions.

Module Lemmas_of_Assertion_Sub_Spec_Lemma_Module.

Import Assertion_D.

Lemma Aexp_max_var_to_aexp_occur : forall a x, (aexp_max_var a < x)%nat -> aexp_occur x a = 0%nat
with Term_max_var_to_term_occur : forall t x, (term_max_var t < x)%nat -> term_occur x t = 0%nat.
Proof.
{
  clear Aexp_max_var_to_aexp_occur.
  intros.
  induction a; simpl.
  + simpl in H.
    apply Term_max_var_to_term_occur.
    exact H.
  + reflexivity.
  + simpl in H.
    apply Nat.max_lub_lt_iff in H.
    destruct H.
    specialize (IHa1 H).
    specialize (IHa2 H0).
    rewrite IHa1.
    rewrite IHa2.
    omega.
  + simpl in H.
    apply Nat.max_lub_lt_iff in H.
    destruct H.
    specialize (IHa1 H).
    specialize (IHa2 H0).
    rewrite IHa1.
    rewrite IHa2.
    omega.
  + simpl in H.
    apply Nat.max_lub_lt_iff in H.
    destruct H.
    specialize (IHa1 H).
    specialize (IHa2 H0).
    rewrite IHa1.
    rewrite IHa2.
    omega.
}
{
  clear Term_max_var_to_term_occur.
  intros.
  induction t; simpl.
  + reflexivity.
  + simpl in H.
    destruct Nat.eq_dec.
    - omega.
    - reflexivity.
  + simpl in H.
    apply Aexp_max_var_to_aexp_occur.
    exact H.
  + simpl in H.
    apply Nat.max_lub_lt_iff in H.
    destruct H.
    specialize (IHt1 H).
    specialize (IHt2 H0).
    rewrite IHt1.
    rewrite IHt2.
    omega.
  + simpl in H.
    apply Nat.max_lub_lt_iff in H.
    destruct H.
    specialize (IHt1 H).
    specialize (IHt2 H0).
    rewrite IHt1.
    rewrite IHt2.
    omega.
  + simpl in H.
    apply Nat.max_lub_lt_iff in H.
    destruct H.
    specialize (IHt1 H).
    specialize (IHt2 H0).
    rewrite IHt1.
    rewrite IHt2.
    omega.
}
Qed.

Lemma Assn_new_var_to_occur: forall E P x,
  x = new_var P E -> assn_occur x P = O /\ aexp_occur x E = O.
Proof.
  intros.
  unfold new_var in H.
  assert (((Init.Nat.max (assn_max_var P) (aexp_max_var E)) < x)%nat).
  {omega. }
  clear H.
  apply Nat.max_lub_lt_iff in H0.
  destruct H0.
  split.
  + clear H0 E.
    induction P; simpl; simpl in H.
    - apply Nat.max_lub_lt_iff in H.
      destruct H.
      pose proof Term_max_var_to_term_occur _ _ H.
      pose proof Term_max_var_to_term_occur _ _ H0.
      rewrite H1.
      rewrite H2.
      omega.
    - apply Nat.max_lub_lt_iff in H.
      destruct H.
      pose proof Term_max_var_to_term_occur _ _ H.
      pose proof Term_max_var_to_term_occur _ _ H0.
      rewrite H1.
      rewrite H2.
      omega.
    - apply Nat.max_lub_lt_iff in H.
      destruct H.
      pose proof Term_max_var_to_term_occur _ _ H.
      pose proof Term_max_var_to_term_occur _ _ H0.
      rewrite H1.
      rewrite H2.
      omega.
    - induction b; simpl.
      * reflexivity.
      * reflexivity.
      * simpl in H.
        apply Nat.max_lub_lt_iff in H.
        destruct H.
        pose proof Aexp_max_var_to_aexp_occur _ _ H.
        pose proof Aexp_max_var_to_aexp_occur _ _ H0.
        rewrite H1.
        rewrite H2.
        omega.
      * simpl in H.
        apply Nat.max_lub_lt_iff in H.
        destruct H.
        pose proof Aexp_max_var_to_aexp_occur _ _ H.
        pose proof Aexp_max_var_to_aexp_occur _ _ H0.
        rewrite H1.
        rewrite H2.
        omega.
      * simpl in H.
        specialize (IHb H).
        exact IHb.
      * simpl in H.
        apply Nat.max_lub_lt_iff in H.
        destruct H.
        specialize (IHb1 H).
        specialize (IHb2 H0).
        rewrite IHb1.
        rewrite IHb2.
        omega.
    - apply Nat.max_lub_lt_iff in H.
      destruct H.
      specialize (IHP1 H).
      specialize (IHP2 H0).
      rewrite IHP1.
      rewrite IHP2.
      omega.
    - apply Nat.max_lub_lt_iff in H.
      destruct H.
      specialize (IHP1 H).
      specialize (IHP2 H0).
      rewrite IHP1.
      rewrite IHP2.
      omega.
    - apply Nat.max_lub_lt_iff in H.
      destruct H.
      specialize (IHP1 H).
      specialize (IHP2 H0).
      rewrite IHP1.
      rewrite IHP2.
      omega.
    - specialize (IHP H).
      exact IHP.
    - apply Nat.max_lub_lt_iff in H.
      destruct H.
      specialize (IHP H0).
      rewrite IHP.
      destruct Nat.eq_dec.
      * omega.
      * reflexivity.
    - apply Nat.max_lub_lt_iff in H.
      destruct H.
      specialize (IHP H0).
      rewrite IHP.
      destruct Nat.eq_dec.
      * omega.
      * reflexivity.
  + apply Aexp_max_var_to_aexp_occur.
    exact H0.
Qed.

Lemma term_occur_zero_add_zero: forall x t1 t2,
(term_occur x t1 + term_occur x t2)%nat = O -> term_occur x t1 = O /\ term_occur x t2 = O.
Proof.
  intros.
  omega.
Qed.

Lemma aexp_occur_zero_add_zero: forall x t1 t2,
(aexp_occur x t1 + aexp_occur x t2)%nat = O -> aexp_occur x t1 = O /\ aexp_occur x t2 = O.
Proof.
  intros.
  omega.
Qed.

Lemma bexp_occur_zero_add_zero: forall x t1 t2,
(bexp_occur x t1 + bexp_occur x t2)%nat = O -> bexp_occur x t1 = O /\ bexp_occur x t2 = O.
Proof.
  intros.
  omega.
Qed.

Lemma assn_occur_zero_add_zero: forall x t1 t2,
(assn_occur x t1 + assn_occur x t2)%nat = O -> assn_occur x t1 = O /\ assn_occur x t2 = O.
Proof.
  intros.
  omega.
Qed.

Lemma No_aexp_occur_to_aexp_rename :
forall st La x y v a, aexp_occur y a = O ->
aexp'_denote (st, Lassn_update La x v) a =
aexp'_denote (st, Lassn_update La y v) (aexp_rename x y a)
with No_term_occur_to_term_rename :
forall st La x y v t, term_occur y t = O ->
term_denote (st, Lassn_update La x v) t =
term_denote (st, Lassn_update La y v) (term_rename x y t).
Proof.
{
  clear No_aexp_occur_to_aexp_rename.
  intros.
  induction a; simpl; simpl in H.
  + pose proof No_term_occur_to_term_rename st La x y v t H.
    exact H0.
  + reflexivity.
  + apply aexp_occur_zero_add_zero in H; destruct H.
    specialize (IHa1 H).
    specialize (IHa2 H0).
    rewrite IHa1.
    rewrite IHa2.
    reflexivity.
  + apply aexp_occur_zero_add_zero in H; destruct H.
    specialize (IHa1 H).
    specialize (IHa2 H0).
    rewrite IHa1.
    rewrite IHa2.
    reflexivity.
  + apply aexp_occur_zero_add_zero in H; destruct H.
    specialize (IHa1 H).
    specialize (IHa2 H0).
    rewrite IHa1.
    rewrite IHa2.
    reflexivity.
}
{
  clear No_term_occur_to_term_rename.
  intros.
  induction t; simpl; simpl in H.
  + reflexivity.
  + destruct Nat.eq_dec.
    - discriminate.
    - destruct Nat.eq_dec.
      * subst.
        clear H.
        unfold Lassn_update at 1.
        destruct Nat.eq_dec.
        ++ unfold term_denote; simpl.
           unfold Lassn_update.
           destruct Nat.eq_dec.
           -- reflexivity.
           -- omega.
        ++ omega.
      * unfold Lassn_update at 1.
        destruct Nat.eq_dec.
        ++ omega.
        ++ unfold term_denote; simpl.
           unfold Lassn_update.
           destruct Nat.eq_dec.
           -- omega.
           -- reflexivity.
  + pose proof No_aexp_occur_to_aexp_rename st La x y v a H.
    exact H0.
  + apply term_occur_zero_add_zero in H; destruct H.
    specialize (IHt1 H).
    specialize (IHt2 H0).
    rewrite IHt1.
    rewrite IHt2.
    reflexivity.
  + apply term_occur_zero_add_zero in H; destruct H.
    specialize (IHt1 H).
    specialize (IHt2 H0).
    rewrite IHt1.
    rewrite IHt2.
    reflexivity.
  + apply term_occur_zero_add_zero in H; destruct H.
    specialize (IHt1 H).
    specialize (IHt2 H0).
    rewrite IHt1.
    rewrite IHt2.
    reflexivity.
}
Qed.

Lemma No_aexp_occur_to_aexp :
forall st La x v a, aexp_occur x a = O ->
aexp'_denote (st, La) a =
aexp'_denote (st, Lassn_update La x v) a
with No_term_occur_to_term :
forall st La x v t, term_occur x t = O ->
term_denote (st, La) t =
term_denote (st, Lassn_update La x v) t.
Proof.
{
  clear No_aexp_occur_to_aexp.
  intros.
  induction a; simpl; simpl in H.
  + apply No_term_occur_to_term.
    exact H.
  + reflexivity.
  + apply aexp_occur_zero_add_zero in H; destruct H.
    specialize (IHa1 H).
    specialize (IHa2 H0).
    rewrite IHa1.
    rewrite IHa2.
    reflexivity.
  + apply aexp_occur_zero_add_zero in H; destruct H.
    specialize (IHa1 H).
    specialize (IHa2 H0).
    rewrite IHa1.
    rewrite IHa2.
    reflexivity.
  + apply aexp_occur_zero_add_zero in H; destruct H.
    specialize (IHa1 H).
    specialize (IHa2 H0).
    rewrite IHa1.
    rewrite IHa2.
    reflexivity.
}
{
  clear No_term_occur_to_term.
  intros.
  induction t; simpl; simpl in H.
  + reflexivity.
  + destruct Nat.eq_dec.
    - omega.
    - pose proof Lassn_update_spec La x v.
      destruct H0.
      specialize (H1 x0 n).
      exact H1.
  + apply No_aexp_occur_to_aexp.
    exact H.
  + apply term_occur_zero_add_zero in H; destruct H.
    specialize (IHt1 H).
    specialize (IHt2 H0).
    rewrite IHt1.
    rewrite IHt2.
    reflexivity.
  + apply term_occur_zero_add_zero in H; destruct H.
    specialize (IHt1 H).
    specialize (IHt2 H0).
    rewrite IHt1.
    rewrite IHt2.
    reflexivity.
  + apply term_occur_zero_add_zero in H; destruct H.
    specialize (IHt1 H).
    specialize (IHt2 H0).
    rewrite IHt1.
    rewrite IHt2.
    reflexivity.
}
Qed.

Lemma no_occ_satisfies_not_free: forall st La P x v,
  assn_occur x P = O ->
  ((st, La) |== P <-> (st, Lassn_update La x v) |== P).
Proof.
  intros.
  revert La.
  induction P; simpl; simpl in H; intros.
  + apply term_occur_zero_add_zero in H; destruct H.
    pose proof No_term_occur_to_term st La x v t1 H.
    pose proof No_term_occur_to_term st La x v t2 H0.
    rewrite <- H1.
    rewrite <- H2.
    reflexivity.
  + apply term_occur_zero_add_zero in H; destruct H.
    pose proof No_term_occur_to_term st La x v t1 H.
    pose proof No_term_occur_to_term st La x v t2 H0.
    rewrite <- H1.
    rewrite <- H2.
    reflexivity.
  + apply term_occur_zero_add_zero in H; destruct H.
    pose proof No_term_occur_to_term st La x v t1 H.
    pose proof No_term_occur_to_term st La x v t2 H0.
    rewrite <- H1.
    rewrite <- H2.
    reflexivity.
  + induction b; simpl; simpl in H.
    - reflexivity.
    - reflexivity.
    - apply aexp_occur_zero_add_zero in H; destruct H.
      pose proof No_aexp_occur_to_aexp st La x v a1 H.
      pose proof No_aexp_occur_to_aexp st La x v a2 H0.
      rewrite <- H1.
      rewrite <- H2.
      reflexivity.
    - apply aexp_occur_zero_add_zero in H; destruct H.
      pose proof No_aexp_occur_to_aexp st La x v a1 H.
      pose proof No_aexp_occur_to_aexp st La x v a2 H0.
      rewrite <- H1.
      rewrite <- H2.
      reflexivity.
    - specialize (IHb H).
      rewrite IHb.
      reflexivity.
    - apply bexp_occur_zero_add_zero in H; destruct H.
      specialize (IHb1 H).
      specialize (IHb2 H0).
      rewrite IHb1.
      rewrite IHb2.
      reflexivity.
  + apply assn_occur_zero_add_zero in H; destruct H.
    specialize (IHP1 H La).
    specialize (IHP2 H0 La).
    rewrite IHP1.
    rewrite IHP2.
    reflexivity.
  + apply assn_occur_zero_add_zero in H; destruct H.
    specialize (IHP1 H La).
    specialize (IHP2 H0 La).
    rewrite IHP1.
    rewrite IHP2.
    reflexivity.
  + apply assn_occur_zero_add_zero in H; destruct H.
    specialize (IHP1 H La).
    specialize (IHP2 H0 La).
    rewrite IHP1.
    rewrite IHP2.
    reflexivity.
  + specialize (IHP H La).
    rewrite IHP.
    reflexivity.
  + destruct Nat.eq_dec.
    - omega.
    - specialize (IHP H).
      unfold Interp_Lupdate; simpl.
      split; intros;
      destruct H0 as [v0 ?];
      exists v0.
      * pose proof Lassn_update_update_diff st La x x0 v v0 n.
        pose proof satisfies_Interp_Equiv (st, Lassn_update (Lassn_update La x v) x0 v0) (st, Lassn_update (Lassn_update La x0 v0) x v) P H1.
        rewrite H2.
        clear H1 H2.
        specialize (IHP (Lassn_update La x0 v0)).
        rewrite <- IHP.
        exact H0.
      * pose proof Lassn_update_update_diff st La x x0 v v0 n.
        pose proof satisfies_Interp_Equiv (st, Lassn_update (Lassn_update La x v) x0 v0) (st, Lassn_update (Lassn_update La x0 v0) x v) P H1.
        rewrite H2 in H0.
        clear H1 H2.
        specialize (IHP (Lassn_update La x0 v0)).
        rewrite IHP.
        exact H0.
  + destruct Nat.eq_dec.
    - discriminate.
    - specialize (IHP H).
      unfold Interp_Lupdate; simpl.
      split; intros.
      * specialize (H0 v0).
        pose proof Lassn_update_update_diff st La x x0 v v0 n.
        pose proof satisfies_Interp_Equiv (st, Lassn_update (Lassn_update La x v) x0 v0) (st, Lassn_update (Lassn_update La x0 v0) x v) P H1.
        rewrite H2.
        clear H1 H2.
        specialize (IHP (Lassn_update La x0 v0)).
        rewrite <- IHP.
        exact H0.
      * specialize (H0 v0).
        pose proof Lassn_update_update_diff st La x x0 v v0 n.
        pose proof satisfies_Interp_Equiv (st, Lassn_update (Lassn_update La x v) x0 v0) (st, Lassn_update (Lassn_update La x0 v0) x v) P H1.
        rewrite H2 in H0.
        clear H1 H2.
        specialize (IHP (Lassn_update La x0 v0)).
        rewrite IHP.
        exact H0.
Qed.

Lemma Assn_rename_assertion_iff : forall st La P E v x,
(st, Lassn_update La x v) |== P <->
(st, Lassn_update La (new_var P E) v) |== assn_rename x (new_var P E) P.
Proof.
  intros.
  remember (new_var P E) as y.
  pose proof Assn_new_var_to_occur _ _ _ Heqy.
  destruct H.
  clear Heqy H0 E.
  revert La x y v H.
  induction P; intros; simpl; simpl in H.
  + apply term_occur_zero_add_zero in H; destruct H.
    pose proof No_term_occur_to_term_rename st La x y v t1 H.
    pose proof No_term_occur_to_term_rename st La x y v t2 H0.
    rewrite <- H1.
    rewrite <- H2.
    reflexivity.
  + apply term_occur_zero_add_zero in H; destruct H.
    pose proof No_term_occur_to_term_rename st La x y v t1 H.
    pose proof No_term_occur_to_term_rename st La x y v t2 H0.
    rewrite <- H1.
    rewrite <- H2.
    reflexivity.
  + apply term_occur_zero_add_zero in H; destruct H.
    pose proof No_term_occur_to_term_rename st La x y v t1 H.
    pose proof No_term_occur_to_term_rename st La x y v t2 H0.
    rewrite <- H1.
    rewrite <- H2.
    reflexivity.
  + induction b; simpl in H; simpl.
    - reflexivity.
    - reflexivity.
    - apply aexp_occur_zero_add_zero in H; destruct H.
      pose proof No_aexp_occur_to_aexp_rename st La x y v a1 H.
      pose proof No_aexp_occur_to_aexp_rename st La x y v a2 H0.
      rewrite <- H1.
      rewrite <- H2.
      reflexivity.
    - apply aexp_occur_zero_add_zero in H; destruct H.
      pose proof No_aexp_occur_to_aexp_rename st La x y v a1 H.
      pose proof No_aexp_occur_to_aexp_rename st La x y v a2 H0.
      rewrite <- H1.
      rewrite <- H2.
      reflexivity.
    - specialize (IHb H).
      rewrite IHb.
      reflexivity.
    - apply bexp_occur_zero_add_zero in H; destruct H.
      pose proof IHb1 H.
      pose proof IHb2 H0.
      rewrite <- H1.
      rewrite <- H2.
      reflexivity.
  + apply assn_occur_zero_add_zero in H; destruct H.
    pose proof IHP1 La x y v H.
    pose proof IHP2 La x y v H0.
    rewrite <- H1.
    rewrite <- H2.
    reflexivity.
  + apply assn_occur_zero_add_zero in H; destruct H.
    pose proof IHP1 La x y v H.
    pose proof IHP2 La x y v H0.
    rewrite <- H1.
    rewrite <- H2.
    reflexivity.
  + apply assn_occur_zero_add_zero in H; destruct H.
    pose proof IHP1 La x y v H.
    pose proof IHP2 La x y v H0.
    rewrite <- H1.
    rewrite <- H2.
    reflexivity.
  + pose proof IHP La x y v H.
    rewrite <- H0.
    reflexivity.
  + destruct Nat.eq_dec in H.
    - discriminate.
    - destruct Nat.eq_dec.
      * simpl.
        subst.
        split; intros;
        destruct H0 as [v0 ?];
        exists v0.
        ++ unfold Interp_Lupdate; simpl.
           unfold Interp_Lupdate in H0; simpl in H0.
           pose proof Lassn_update_update_same st La x v v0.
           pose proof satisfies_Interp_Equiv (st, Lassn_update (Lassn_update La x v) x v0) (st, Lassn_update La x v0) P H1.
           rewrite H2 in H0.
           clear H1 H2.
           pose proof Lassn_update_update_diff st La y x v v0 n.
           pose proof satisfies_Interp_Equiv (st, Lassn_update (Lassn_update La y v) x v0) (st, Lassn_update (Lassn_update La x v0) y v) P H1.
           apply H2.
           clear H1 H2.
           pose proof no_occ_satisfies_not_free st (Lassn_update La x v0) P y v H.
           rewrite <- H1.
           exact H0.
        ++ pose proof Lassn_update_update_same st La x v v0.
           unfold Interp_Lupdate; simpl.
           unfold Interp_Lupdate in H0; simpl in H0.
           pose proof satisfies_Interp_Equiv (st, Lassn_update (Lassn_update La x v) x v0) (st, Lassn_update La x v0) P H1.
           rewrite H2.
           clear H1 H2.
           pose proof Lassn_update_update_diff st La y x v v0 n.
           pose proof satisfies_Interp_Equiv (st, Lassn_update (Lassn_update La y v) x v0) (st, Lassn_update (Lassn_update La x v0) y v) P H1.
           apply H2 in H0.
           clear H1 H2.
           pose proof no_occ_satisfies_not_free st (Lassn_update La x v0) P y v H.
           rewrite H1.
           exact H0.
      * simpl.
        unfold Interp_Lupdate; simpl.
        split; intros;
        destruct H0 as [v0 ?];
        exists v0.
        ++ pose proof IHP (Lassn_update La x v0) x0 y v H.
           pose proof Lassn_update_update_diff st La x0 x v v0 n0.
           pose proof satisfies_Interp_Equiv (st, Lassn_update (Lassn_update La x0 v) x v0) (st, Lassn_update (Lassn_update La x v0) x0 v) P H2.
           rewrite H3 in H0.
           rewrite H1 in H0.
           clear H1 H2 H3.
           pose proof Lassn_update_update_diff st La y x v v0 n.
           pose proof satisfies_Interp_Equiv (st, Lassn_update (Lassn_update La y v) x v0) (st, Lassn_update (Lassn_update La x v0) y v) (assn_rename x0 y P) H1.
           rewrite H2.
           exact H0.
        ++ pose proof IHP (Lassn_update La x v0) x0 y v H.
           pose proof Lassn_update_update_diff st La x0 x v v0 n0.
           pose proof satisfies_Interp_Equiv (st, Lassn_update (Lassn_update La x0 v) x v0) (st, Lassn_update (Lassn_update La x v0) x0 v) P H2.
           rewrite H3.
           rewrite H1.
           clear H1 H2 H3.
           pose proof Lassn_update_update_diff st La y x v v0 n.
           pose proof satisfies_Interp_Equiv (st, Lassn_update (Lassn_update La y v) x v0) (st, Lassn_update (Lassn_update La x v0) y v) (assn_rename x0 y P) H1.
           rewrite H2 in H0.
           exact H0.
  + destruct Nat.eq_dec in H.
    - discriminate.
    - destruct Nat.eq_dec.
      * simpl.
        subst.
        split; intros;
        specialize (H0 v0).
        ++ pose proof Lassn_update_update_same st La x v v0.
           unfold Interp_Lupdate; simpl.
           pose proof satisfies_Interp_Equiv (st, Lassn_update (Lassn_update La x v) x v0) (st, Lassn_update La x v0) P H1.
           rewrite H2 in H0.
           clear H1 H2.
           pose proof Lassn_update_update_diff st La y x v v0 n.
           pose proof satisfies_Interp_Equiv (st, Lassn_update (Lassn_update La y v) x v0) (st, Lassn_update (Lassn_update La x v0) y v) P H1.
           apply H2.
           clear H1 H2.
           pose proof no_occ_satisfies_not_free st (Lassn_update La x v0) P y v H.
           rewrite <- H1.
           exact H0.
        ++ pose proof Lassn_update_update_same st La x v v0.
           unfold Interp_Lupdate; simpl.
           unfold Interp_Lupdate in H0; simpl in H0.
           pose proof satisfies_Interp_Equiv (st, Lassn_update (Lassn_update La x v) x v0) (st, Lassn_update La x v0) P H1.
           rewrite H2.
           clear H1 H2.
           pose proof Lassn_update_update_diff st La y x v v0 n.
           pose proof satisfies_Interp_Equiv (st, Lassn_update (Lassn_update La y v) x v0) (st, Lassn_update (Lassn_update La x v0) y v) P H1.
           apply H2 in H0.
           clear H1 H2.
           pose proof no_occ_satisfies_not_free st (Lassn_update La x v0) P y v H.
           rewrite H1.
           exact H0.
      * simpl.
        unfold Interp_Lupdate; simpl.
        split; intros;
        specialize (H0 v0).
        ++ pose proof IHP (Lassn_update La x v0) x0 y v H.
           pose proof Lassn_update_update_diff st La x0 x v v0 n0.
           pose proof satisfies_Interp_Equiv (st, Lassn_update (Lassn_update La x0 v) x v0) (st, Lassn_update (Lassn_update La x v0) x0 v) P H2.
           rewrite H3 in H0.
           rewrite H1 in H0.
           clear H1 H2 H3.
           pose proof Lassn_update_update_diff st La y x v v0 n.
           pose proof satisfies_Interp_Equiv (st, Lassn_update (Lassn_update La y v) x v0) (st, Lassn_update (Lassn_update La x v0) y v) (assn_rename x0 y P) H1.
           rewrite H2.
           exact H0.
        ++ pose proof IHP (Lassn_update La x v0) x0 y v H.
           pose proof Lassn_update_update_diff st La x0 x v v0 n0.
           pose proof satisfies_Interp_Equiv (st, Lassn_update (Lassn_update La x0 v) x v0) (st, Lassn_update (Lassn_update La x v0) x0 v) P H2.
           rewrite H3.
           rewrite H1.
           clear H1 H2 H3.
           pose proof Lassn_update_update_diff st La y x v v0 n.
           pose proof satisfies_Interp_Equiv (st, Lassn_update (Lassn_update La y v) x v0) (st, Lassn_update (Lassn_update La x v0) y v) (assn_rename x0 y P) H1.
           rewrite H2 in H0.
           exact H0.
Qed.

Lemma Term_denote_term_sub: forall st1 st2 La X E t,
  st2 X = aexp'_denote (st1, La) E ->
  (forall Y : var, X <> Y -> st1 Y = st2 Y) ->
  term_denote (st1, La) (term_sub X E t) = term_denote (st2, La) t
with Aexp_denote_aexp_sub: forall st1 st2 La X E a,
  st2 X = aexp'_denote (st1, La) E ->
  (forall Y : var, X <> Y -> st1 Y = st2 Y) ->
  aexp'_denote (st1, La) (aexp_sub X E a) = aexp'_denote (st2, La) a.
Proof.
{
  clear Term_denote_term_sub.
  intros.
  induction t; simpl.
  + reflexivity.
  + reflexivity.
  + pose proof Aexp_denote_aexp_sub st1 st2 La X E a H H0.
    exact H1.
  + rewrite IHt1.
    rewrite IHt2.
    reflexivity.
  + rewrite IHt1.
    rewrite IHt2.
    reflexivity.
  + rewrite IHt1.
    rewrite IHt2.
    reflexivity.
}
{
  clear Aexp_denote_aexp_sub.
  intros.
  induction a; simpl.
  + pose proof Term_denote_term_sub st1 st2 La X E t H H0.
    exact H1.
  + destruct Nat.eq_dec.
    - rewrite <- H.
      rewrite e.
      reflexivity.
    - firstorder.
  + rewrite IHa1.
    rewrite IHa2.
    reflexivity.
  + rewrite IHa1.
    rewrite IHa2.
    reflexivity.
  + rewrite IHa1.
    rewrite IHa2.
    reflexivity.
}
Qed.

End Lemmas_of_Assertion_Sub_Spec_Lemma_Module.


Require Import Arith.

Definition lt_3 (n:nat) : bool :=
  match n with
  | O => true
  | 1 => true
  | 2 => true
  | _ => false
  end.

Eval compute in (lt_3 45).

Eval compute in (lt_3 2).

Print lt_3.


From Test Require Import tactic.

Section FOFProblem.

Variable Universe : Set.
Variable UniverseElement : Universe.

Variable par_ : 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 proposition_31_1 : (forall A B C D : Universe, (exists X Y Z : Universe, ((betS_ B D C /\ ~(col_ B C A)) -> (betS_ X A Y /\ (congA_ Y A D A D B /\ (congA_ Y A D B D A /\ (congA_ D A Y B D A /\ (congA_ X A D A D C /\ (congA_ X A D C D A /\ (congA_ D A X C D A /\ (par_ X Y B C /\ (cong_ X A D C /\ (cong_ A Y B D /\ (cong_ A Z Z D /\ (cong_ X Z Z C /\ (cong_ B Z Z Y /\ (betS_ X Z C /\ (betS_ B Z Y /\ betS_ A Z D)))))))))))))))))).

Theorem proposition_31short_2 : (forall A B C D : Universe, (exists X Y Z : Universe, ((betS_ B D C /\ ~(col_ B C A)) -> (betS_ X A Y /\ (congA_ X A D A D C /\ (par_ X Y B C /\ (betS_ X Z C /\ betS_ A Z D))))))).
Proof.
  time tac.
Qed.

End FOFProblem.


Require Import Omega.

(* ***** UNARY ***** *)

(* Countdown worker function *)
Fixpoint cdn_wkr (f : nat -> nat) (a n k : nat) : nat :=
  match k with
  | 0    => 0
  | S k' => if (n <=? a) then 0 else
              S (cdn_wkr f a (f n) k')
  end.

(* Countdown *)
Definition countdown_to f a n := cdn_wkr f a n n.

(* A function to summarize the initial values of the hyperoperations *)
Definition hyperop_init (a n : nat) : nat :=
  match n with 0 => a | 1 => 0 | _ => 1 end.

(* The inverse hyperop hierarchy *)
Fixpoint inv_hyperop a n b :=
  match n with
  | 0 => b - 1
  | S n' =>
    countdown_to (inv_hyperop a n') (hyperop_init a n') b
  end.

(* ***** BINARY ***** *)

Open Scope N_scope.

(* Supporting function - Used to compute the budget for bin_cdn_wkr and inv_ack_wrk
   Returns length of the binary representation in type "nat" *)
Definition nat_size (n : N) : nat :=
  match n with
  | 0 => 0%nat
  | Npos p => let fix nat_pos_size (x : positive) : nat :=
                  match x with
                  | xH => 1%nat
                  | xI y | xO y => S (nat_pos_size y) end
              in nat_pos_size p
  end.

(* Countdown worker function *)
Fixpoint bin_cdn_wkr (f : N -> N) (a n : N) (b : nat) : N :=
  match b with
  | O    => 0
  | S b' => if (n <=? a) then 0
            else 1 + bin_cdn_wkr f a (f n) b'
  end.

(* Countdown *)
Definition bin_countdown_to (f : N -> N) (a n : N) : N :=
  bin_cdn_wkr f a n (nat_size (n - a)).

Fixpoint bin_inv_hyperop (a : N) (n : nat) (b : N) :=
  match n with
  | 0%nat => b - 1
  | 1%nat => b - a
  | 2%nat => (b + a - 1) / a
  | S n'  => bin_countdown_to (bin_inv_hyperop a n') 1 b
  end.

Close Scope N_scope.


(************************************************************************)
(*  v      *   The Coq Proof Assistant  /  The Coq Development Team     *)
(* <O___,, *   INRIA - CNRS - LIX - LRI - PPS - Copyright 1999-2010     *)
(*   \VV/  **************************************************************)
(*    //   *      This file is distributed under the terms of the       *)
(*         *       GNU Lesser General Public License Version 2.1        *)
(************************************************************************)
(*                      Evgeny Makarov, INRIA, 2007                     *)
(************************************************************************)

Require Import Bool. (* To get the orb and negb function *)
Require Import RelationPairs.
Require Export NStrongRec.

(** In this module, we derive generic implementations of usual operators
   just via the use of a [recursion] function. *)

Module NdefOpsProp (Import N : NAxiomsRecSig').
Include NStrongRecProp N.

(** Nullity Test *)

Definition if_zero (A : Type) (a b : A) (n : N.t) : A :=
  recursion a (fun _ _ => b) n.

Implicit Arguments if_zero [A].

Instance if_zero_wd (A : Type) :
 Proper (Logic.eq ==> Logic.eq ==> N.eq ==> Logic.eq) (@if_zero A).
Proof.
unfold if_zero. (* TODO : solve_proper : SLOW + BUG *)
f_equiv'.
Qed.

Theorem if_zero_0 : forall (A : Type) (a b : A), if_zero a b 0 = a.
Proof.
unfold if_zero; intros; now rewrite recursion_0.
Qed.

Theorem if_zero_succ :
 forall (A : Type) (a b : A) (n : N.t), if_zero a b (S n) = b.
Proof.
intros; unfold if_zero.
now rewrite recursion_succ.
Qed.

(*****************************************************)
(**                   Addition                       *)

Definition def_add (x y : N.t) := recursion y (fun _ => S) x.

Local Infix "+++" := def_add (at level 50, left associativity).

Instance def_add_wd : Proper (N.eq ==> N.eq ==> N.eq) def_add.
Proof.
unfold def_add. f_equiv'.
Qed.

Theorem def_add_0_l : forall y, 0 +++ y == y.
Proof.
intro y. unfold def_add. now rewrite recursion_0.
Qed.

Theorem def_add_succ_l : forall x y, S x +++ y == S (x +++ y).
Proof.
intros x y; unfold def_add.
rewrite recursion_succ; f_equiv'.
Qed.

Theorem def_add_add : forall n m, n +++ m == n + m.
Proof.
intros n m; induct n.
now rewrite def_add_0_l, add_0_l.
intros n H. now rewrite def_add_succ_l, add_succ_l, H.
Qed.

(*****************************************************)
(**                  Multiplication                  *)

Definition def_mul (x y : N.t) := recursion 0 (fun _ p => p +++ x) y.

Local Infix "**" := def_mul (at level 40, left associativity).

Instance def_mul_wd : Proper (N.eq ==> N.eq ==> N.eq) def_mul.
Proof.
unfold def_mul. (* TODO : solve_proper SLOW + BUG *)
f_equiv'.
Qed.

Theorem def_mul_0_r : forall x, x ** 0 == 0.
Proof.
intro. unfold def_mul. now rewrite recursion_0.
Qed.

Theorem def_mul_succ_r : forall x y, x ** S y == x ** y +++ x.
Proof.
intros x y; unfold def_mul.
rewrite recursion_succ; auto with *.
f_equiv'.
Qed.

Theorem def_mul_mul : forall n m, n ** m == n * m.
Proof.
intros n m; induct m.
now rewrite def_mul_0_r, mul_0_r.
intros m IH; now rewrite def_mul_succ_r, mul_succ_r, def_add_add, IH.
Qed.

(*****************************************************)
(**                     Order                        *)

Definition ltb (m : N.t) : N.t -> bool :=
recursion
  (if_zero false true)
  (fun _ f n => recursion false (fun n' _ => f n') n)
  m.

Local Infix "<<" := ltb (at level 70, no associativity).

Instance ltb_wd : Proper (N.eq ==> N.eq ==> Logic.eq) ltb.
Proof.
unfold ltb. f_equiv'.
Qed.

Theorem ltb_base : forall n, 0 << n = if_zero false true n.
Proof.
intro n; unfold ltb; now rewrite recursion_0.
Qed.

Theorem ltb_step :
  forall m n, S m << n = recursion false (fun n' _ => m << n') n.
Proof.
intros m n; unfold ltb at 1.
f_equiv.
rewrite recursion_succ; f_equiv'.
reflexivity.
Qed.

(* Above, we rewrite applications of function. Is it possible to rewrite
functions themselves, i.e., rewrite (recursion lt_base lt_step (S n)) to
lt_step n (recursion lt_base lt_step n)? *)

Theorem ltb_0 : forall n, n << 0 = false.
Proof.
cases n.
rewrite ltb_base; now rewrite if_zero_0.
intro n; rewrite ltb_step. now rewrite recursion_0.
Qed.

Theorem ltb_0_succ : forall n, 0 << S n = true.
Proof.
intro n; rewrite ltb_base; now rewrite if_zero_succ.
Qed.

Theorem succ_ltb_mono : forall n m, (S n << S m) = (n << m).
Proof.
intros n m.
rewrite ltb_step. rewrite recursion_succ; f_equiv'.
Qed.

Theorem ltb_lt : forall n m, n << m = true <-> n < m.
Proof.
double_induct n m.
cases m.
rewrite ltb_0. split; intro H; [discriminate H | false_hyp H nlt_0_r].
intro n. rewrite ltb_0_succ. split; intro; [apply lt_0_succ | reflexivity].
intro n. rewrite ltb_0. split; intro H; [discriminate | false_hyp H nlt_0_r].
intros n m. rewrite succ_ltb_mono. now rewrite <- succ_lt_mono.
Qed.

Theorem ltb_ge : forall n m, n << m = false <-> n >= m.
Proof.
intros. rewrite <- not_true_iff_false, ltb_lt. apply nlt_ge.
Qed.

(*****************************************************)
(**                     Even                         *)

Definition even (x : N.t) := recursion true (fun _ p => negb p) x.

Instance even_wd : Proper (N.eq==>Logic.eq) even.
Proof.
unfold even. f_equiv'.
Qed.

Theorem even_0 : even 0 = true.
Proof.
unfold even.
now rewrite recursion_0.
Qed.

Theorem even_succ : forall x, even (S x) = negb (even x).
Proof.
unfold even.
intro x; rewrite recursion_succ; f_equiv'.
Qed.

(*****************************************************)
(**                Division by 2                     *)

Definition half_aux (x : N.t) : N.t * N.t :=
  recursion (0, 0) (fun _ p => let (x1, x2) := p in (S x2, x1)) x.

Definition half (x : N.t) := snd (half_aux x).

Instance half_aux_wd : Proper (N.eq ==> N.eq*N.eq) half_aux.
Proof.
intros x x' Hx. unfold half_aux.
f_equiv; trivial.
intros y y' Hy (u,v) (u',v') (Hu,Hv). compute in *.
rewrite Hu, Hv; auto with *.
Qed.

Instance half_wd : Proper (N.eq==>N.eq) half.
Proof.
unfold half. f_equiv'.
Qed.

Lemma half_aux_0 : half_aux 0 = (0,0).
Proof.
unfold half_aux. rewrite recursion_0; auto.
Qed.

Lemma half_aux_succ : forall x,
 half_aux (S x) = (S (snd (half_aux x)), fst (half_aux x)).
Proof.
intros.
remember (half_aux x) as h.
destruct h as (f,s); simpl in *.
unfold half_aux in *.
rewrite recursion_succ, <- Heqh; simpl; f_equiv'.
Qed.

Theorem half_aux_spec : forall n,
 n == fst (half_aux n) + snd (half_aux n).
Proof.
apply induction.
intros x x' Hx. setoid_rewrite Hx; auto with *.
rewrite half_aux_0; simpl; rewrite add_0_l; auto with *.
intros.
rewrite half_aux_succ. simpl.
rewrite add_succ_l, add_comm; auto.
now f_equiv.
Qed.

Theorem half_aux_spec2 : forall n,
 fst (half_aux n) == snd (half_aux n) \/
 fst (half_aux n) == S (snd (half_aux n)).
Proof.
apply induction.
intros x x' Hx. setoid_rewrite Hx; auto with *.
rewrite half_aux_0; simpl. auto with *.
intros.
rewrite half_aux_succ; simpl.
destruct H; auto with *.
right; now f_equiv.
Qed.

Theorem half_0 : half 0 == 0.
Proof.
unfold half. rewrite half_aux_0; simpl; auto with *.
Qed.

Theorem half_1 : half 1 == 0.
Proof.
unfold half. rewrite one_succ, half_aux_succ, half_aux_0; simpl; auto with *.
Qed.

Theorem half_double : forall n,
 n == 2 * half n \/ n == 1 + 2 * half n.
Proof.
intros. unfold half.
nzsimpl'.
destruct (half_aux_spec2 n) as [H|H]; [left|right].
rewrite <- H at 1. apply half_aux_spec.
rewrite <- add_succ_l. rewrite <- H at 1. apply half_aux_spec.
Qed.

Theorem half_upper_bound : forall n, 2 * half n <= n.
Proof.
intros.
destruct (half_double n) as [E|E]; rewrite E at 2.
apply le_refl.
nzsimpl.
apply le_le_succ_r, le_refl.
Qed.

Theorem half_lower_bound : forall n, n <= 1 + 2 * half n.
Proof.
intros.
destruct (half_double n) as [E|E]; rewrite E at 1.
nzsimpl.
apply le_le_succ_r, le_refl.
apply le_refl.
Qed.

Theorem half_nz : forall n, 1 < n -> 0 < half n.
Proof.
intros n LT.
assert (LE : 0 <= half n) by apply le_0_l.
le_elim LE; auto.
destruct (half_double n) as [E|E];
 rewrite <- LE, mul_0_r, ?add_0_r in E; rewrite E in LT.
order'.
order.
Qed.

Theorem half_decrease : forall n, 0 < n -> half n < n.
Proof.
intros n LT.
destruct (half_double n) as [E|E]; rewrite E at 2; nzsimpl'.
rewrite <- add_0_l at 1.
rewrite <- add_lt_mono_r.
assert (LE : 0 <= half n) by apply le_0_l.
le_elim LE; auto.
rewrite <- LE, mul_0_r in E. rewrite E in LT. destruct (nlt_0_r _ LT).
rewrite <- add_succ_l.
rewrite <- add_0_l at 1.
rewrite <- add_lt_mono_r.
apply lt_0_succ.
Qed.

(*****************************************************)
(**            Power                                 *)

Definition pow (n m : N.t) := recursion 1 (fun _ r => n*r) m.

Local Infix "^^" := pow (at level 30, right associativity).

Instance pow_wd : Proper (N.eq==>N.eq==>N.eq) pow.
Proof.
unfold pow. f_equiv'.
Qed.

Lemma pow_0 : forall n, n^^0 == 1.
Proof.
intros. unfold pow. rewrite recursion_0. auto with *.
Qed.

Lemma pow_succ : forall n m, n^^(S m) == n*(n^^m).
Proof.
intros. unfold pow. rewrite recursion_succ; f_equiv'.
Qed.

(*****************************************************)
(**            Logarithm for the base 2              *)

Definition log (x : N.t) : N.t :=
strong_rec 0
           (fun g x =>
              if x << 2 then 0
              else S (g (half x)))
           x.

Instance log_prewd :
 Proper ((N.eq==>N.eq)==>N.eq==>N.eq)
   (fun g x => if x<<2 then 0 else S (g (half x))).
Proof.
intros g g' Hg n n' Hn.
rewrite Hn.
destruct (n' << 2); auto with *.
f_equiv. apply Hg. now f_equiv.
Qed.

Instance log_wd : Proper (N.eq==>N.eq) log.
Proof.
intros x x' Exx'. unfold log.
apply strong_rec_wd; f_equiv'.
Qed.

Lemma log_good_step : forall n h1 h2,
 (forall m, m < n -> h1 m == h2 m) ->
  (if n << 2 then 0 else S (h1 (half n))) ==
  (if n << 2 then 0 else S (h2 (half n))).
Proof.
intros n h1 h2 E.
destruct (n<<2) as [ ]_eqn:H.
auto with *.
f_equiv. apply E, half_decrease.
rewrite two_succ, <- not_true_iff_false, ltb_lt, nlt_ge, le_succ_l in H.
order'.
Qed.
Hint Resolve log_good_step.

Theorem log_init : forall n, n < 2 -> log n == 0.
Proof.
intros n Hn. unfold log. rewrite strong_rec_fixpoint; auto with *.
replace (n << 2) with true; auto with *.
symmetry. now rewrite ltb_lt.
Qed.

Theorem log_step : forall n, 2 <= n -> log n == S (log (half n)).
Proof.
intros n Hn. unfold log. rewrite strong_rec_fixpoint; auto with *.
replace (n << 2) with false; auto with *.
symmetry. rewrite <- not_true_iff_false, ltb_lt, nlt_ge; auto.
Qed.

Theorem pow2_log : forall n, 0 < n -> half n < 2^^(log n) <= n.
Proof.
intro n; generalize (le_refl n). set (k:=n) at -2. clearbody k.
revert k. pattern n. apply induction; clear n.
intros n n' Hn; setoid_rewrite Hn; auto with *.
intros k Hk1 Hk2.
 le_elim Hk1. destruct (nlt_0_r _ Hk1).
 rewrite Hk1 in Hk2. destruct (nlt_0_r _ Hk2).

intros n IH k Hk1 Hk2.
destruct (lt_ge_cases k 2) as [LT|LE].
(* base *)
rewrite log_init, pow_0 by auto.
rewrite <- le_succ_l, <- one_succ in Hk2.
le_elim Hk2.
rewrite two_succ, <- nle_gt, le_succ_l in LT. destruct LT; auto.
rewrite <- Hk2.
rewrite half_1; auto using lt_0_1, le_refl.
(* step *)
rewrite log_step, pow_succ by auto.
rewrite two_succ, le_succ_l in LE.
destruct (IH (half k)) as (IH1,IH2).
 rewrite <- lt_succ_r. apply lt_le_trans with k; auto.
  now apply half_decrease.
 apply half_nz; auto.
set (K:=2^^log (half k)) in *; clearbody K.
split.
rewrite <- le_succ_l in IH1.
apply mul_le_mono_l with (p:=2) in IH1.
eapply lt_le_trans; eauto.
nzsimpl'.
rewrite lt_succ_r.
eapply le_trans; [ eapply half_lower_bound | ].
nzsimpl'; apply le_refl.
eapply le_trans; [ | eapply half_upper_bound ].
apply mul_le_mono_l; auto.
Qed.

End NdefOpsProp.


Require Import FMaps NArith MultiSets FMapListEq.

(* If we have:
    - A UsualOrderedType for elements (i.e. a decidable order
       compatible with Coq equality).
    - Proof Irrelevance axiom
   then we can build Multisets relying on Finite Maps implemented
   as sorted lists, in such a way that the equality on Multisets
   is the usual equality of Coq.
*)

Module UsualEqual (U:UsualOrderedType).

  Module Import UE:= FMapListEq.UsualEqual U.
  Module Mu := MultiSets.Multi U UE.M.

  Lemma Equal_eq : forall m m' : Mu.t, Mu.Equal m m' -> m = m'.
  Proof.
   intros.
   unfold Mu.t, Mu.Equal, Mu.multi in *.
   apply UE.Equal_eq.
   red; intros.
   generalize (H y); clear H.
   do 2 destruct M.find; inversion 1; auto.
  Qed.

  (* An example of consequence. *)

  Lemma union_commutes : forall m m', Mu.union m m' = Mu.union m' m.
  Proof.
    intros.
    apply Equal_eq.
    red; intros.
    do 2 rewrite Mu.union_1.
    apply Nplus_comm.
  Qed.

End UsualEqual.


Require Import Arith.
Require Import Omega.
Require Import Psatz.

Require Import kernel_numeric.

Notation "'all' i : V , P" := (forall i : nat, i < V -> P) (at level 20, i at level 99).
Notation "'some' i : V , P" := (exists i : nat, i < V /\ P) (at level 20, i at level 99).

Structure Graph := {
  V :> nat ; (* The number of vertices. So the vertices are numbers 0, 1, ..., V-1. *)
  E :> nat -> nat -> Prop ; (* The edge relation *)
  E_decidable : forall x y : nat, ({E x y} + {~ E x y}) ;
  E_irreflexive : all x : V, ~ E x x ;
  E_symmetric : all x : V, all y : V, (E x y -> E y x)
}.

(** The number of edges in a graph. *)
Definition edges (G : Graph) : nat :=
  sum' (V G) (fun x => count x (E_decidable G x)).

(** The degree of a vertex. We define it so that it
    return 0 if we give it a number which is not
    a vertex. *)
Definition degree (G : Graph) (x : nat) :=
  count (V G) (E_decidable G x).

Definition injektivna_funkcijaA (n : nat) (f : nat -> nat) :=
  all x : n, all y : n, (f x = f y -> x = y).

Definition injektivna_funkcijaB (n : nat) (f : nat -> nat) :=
  all x : n, all y : x, not (f x = f y).

Definition injektivna_funkcijaC (n : nat) (f : nat -> nat) :=
  all x : n, all y : x, (not (x = y) -> not (f x = f y)).

Theorem ekvivalenca_injektivnihAB (n : nat) (f : nat -> nat):
  injektivna_funkcijaA n f <-> injektivna_funkcijaB n f.
Proof.
  unfold injektivna_funkcijaA.
  unfold injektivna_funkcijaB.
  split.
  - intro A.
    intros x p y q.
    intro.
    absurd (x = y).
    + omega.
    + apply A; omega.
  - intro B.
    intros x p y q.
    intro.
    destruct (lt_eq_lt_dec x y) as [[P1|P2]|P3]; auto.
    + absurd (f y = f x); auto.
    + absurd (f x = f y); auto.
Qed.

Theorem ekvivalenca_injektivnihBC (n : nat) (f : nat -> nat):
  injektivna_funkcijaB n f <-> injektivna_funkcijaC n f.
Proof.
  unfold injektivna_funkcijaB.
  unfold injektivna_funkcijaC.
  split.
  - auto.
  - intro C.
    intros x p y q.
    apply C; omega.
Qed.

Structure Bijection (A B : Type) := {
  bij_map :> A -> B ;
  bij_inv : B -> A ;
  bij_inv_right : forall x, bij_map (bij_inv x) = x ;
  bij_inv_left : forall y, bij_inv (bij_map y) = y
}.

Structure Injection (A B : Type) := {
  inj_map :> A -> B ;
  inj_def : forall x y : A, (inj_map x = inj_map y -> x = y) ;
}.

Lemma pomozniA (n : nat) (p : n > 1):
  0 < n.
Proof.
  omega.
Qed.

(**
Pozor:
Kaj vse se v tej definiciji skriva
**)

Definition walk_from_to_with_f_n
  (G : Graph) (x y : nat) (P1 : x < V G) (P2 : y < V G) (n : nat) (P3 : 1 < n) :=
  exists f : nat -> nat,
  (f 0 = x /\ f (n-1) = y /\ (all j : n-1, G (f j) (f (j + 1)))).

(*
Definition conected_graphD (G : Graph) :
  forall (x : nat) (p : x < (V G)), all y : x, some k : (V G),
  (k > 1 /\ exists f : (nat -> nat), (walk_from_to_with_f_n G x y p _ k _)).

Definition graph_path_of_n
  (G : Graph) (n : nat) (p : n > 1) (f : nat -> nat) (Inj_f : injektivna_funkcijaB n f) :=
  (all x : n-1, G (f x) (f (x + 1))).

Definition connected_graphA (G : Graph):
  all x : (V G), all y : x, some k : (V G), (k > 1 /\ exists f : (nat -> nat),
  (injektivna_funkcijaB k f) /\ f 0 = x /\ f k = y /\
  (graph_path_of_n G k _ f _))
refine.
*)

Theorem edges_symmetry (G : Graph):
  (all x : V G , all y : V G,
  ((if E_decidable G x y then 1 else 0) =
  (if E_decidable G y x then 1 else 0))).
Proof.
  intros x p y q.
  destruct (E_decidable G x y); destruct (E_decidable G y x).
  - auto.
  - firstorder using E_symmetric.
  - firstorder using E_symmetric.
  - auto.
Qed.