From bf4412d00991184cab2d8be1d9c8674b5c9b48b0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bj=C3=B6rn=20Brandenburg?= <bbb@mpi-sws.org>
Date: Thu, 27 Jun 2019 23:11:42 +0200
Subject: [PATCH] add utility function search_arg for searching across
 schedules

Given an interval [a, b), a function f: nat -> T, a predicate P, and a
total, reflexive, transitive relation R, [search_arg f P R a b] will
find the x in [a, b) that is an extremum w.r.t. R among all elements x
in [a, b) for which (f x) satisfies P.

For example, this can be used to search in a schedule for a scheduled
job released before some reference time with the earliest deadline.
---
 util/all.v        |   1 +
 util/search_arg.v | 204 ++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 205 insertions(+)
 create mode 100644 util/search_arg.v

diff --git a/util/all.v b/util/all.v
index 892aad02c..987a28eec 100644
--- a/util/all.v
+++ b/util/all.v
@@ -18,3 +18,4 @@ Require Export rt.util.minmax.
 Require Export rt.util.seqset.
 Require Export rt.util.step_function.
 Require Export rt.util.epsilon.
+Require Export rt.util.search_arg.
diff --git a/util/search_arg.v b/util/search_arg.v
new file mode 100644
index 000000000..0761e4a93
--- /dev/null
+++ b/util/search_arg.v
@@ -0,0 +1,204 @@
+From mathcomp Require Import ssreflect ssrbool eqtype ssrnat seq fintype.
+From rt.util Require Import tactics.
+
+(** This file introduces a function called search_arg that allows finding the
+    argument within a given range for which a function is minimal w.r.t. to a
+    given order while satisfying a given predicate, along with lemmas
+    establishing the basic properties of search_arg.
+
+    Note that while this is quite similar to [arg min ...] / [arg max ...] in
+    ssreflect (fintype), this function is subtly different in that it possibly
+    returns None and that it does not require the last element in the given
+    range to satisfy the predicate. In contrast, ssreflect's notion of
+    extremum in fintype uses the upper bound of the search space as the
+    default value, which is rather unnatural when searching through a schedule.
+*)
+
+Section ArgSearch.
+  (* Given a function [f] that maps the naturals to elements of type [T]... *)
+  Context {T: Type}.
+  Variable f: nat -> T.
+
+  (* ... a predicate [P] on [T] ... *)
+  Variable P: pred T.
+
+  (* ... and an order [R] on [T] ... *)
+  Variable R: rel T.
+
+  (* ... we define the procedure [search_arg] to iterate a given search space
+     [a, b), while checking each element whether [f] satisfies [P] at that
+     point and returning the extremum as defined by [R]. *)
+  Fixpoint search_arg (a b: nat): option nat :=
+    if a < b then
+      match b with
+      | 0 => None
+      | S b' => match search_arg a b' with
+                | None => if P (f b') then Some b' else None
+                | Some x => if P (f b') && R (f b') (f x) then Some b' else Some x
+                end
+      end
+    else None.
+
+  (** In the following, we establish basic properties of [search_arg]. *)
+
+  (* To begin, we observe that the search yields None iff predicate [P] does
+     not hold for any of the points in the search interval. *)
+  Lemma search_arg_none:
+    forall a b,
+      search_arg a b = None <-> forall x, a <= x < b -> ~~ P (f x).
+  Proof.
+    split.
+    { (* if *)
+      elim: b => [ _ | b' HYP]; first by move=> _ /andP [_ FALSE] //.
+      rewrite /search_arg  -/search_arg.
+      case: (boolP (a < b'.+1)) => [a_lt_b | not_a_lt_b' TRIV].
+      - move: HYP. case: (search_arg a b') => [y | HYP NIL x].
+        + case: (P (f b') && R (f b') (f y)) => //.
+        + move=> /andP[a_le_x x_lt_b'].
+          move: x_lt_b'.
+          rewrite ltnS leq_eqVlt => /orP [/eqP EQ|LT].
+          * rewrite EQ.
+            move: NIL. case: (P (f b')) => //.
+          * feed HYP => //.
+            apply: (HYP x).
+            by apply /andP; split.
+      - move=> x /andP [a_le_x b_lt_b'].
+        exfalso.
+        move: not_a_lt_b'. rewrite -leqNgt ltnNge => /negP b'_lt_a.
+        by move: (leq_ltn_trans a_le_x b_lt_b').
+    }
+    { (* only if *)
+      rewrite /search_arg.
+      elim: b  => [//|b'].
+      rewrite -/search_arg => IND  NOT_SAT.
+      have ->: search_arg a b' = None.
+      {
+        apply IND => x /andP [a_le_x x_lt_n].
+        apply: (NOT_SAT x).
+        apply /andP; split => //.
+        by rewrite ltnS; apply ltnW.
+      }
+      case: (boolP (a < b'.+1)) => [a_lt_b | //].
+      apply ifF.
+      apply negbTE.
+      apply (NOT_SAT b').
+        by apply /andP; split.
+    }
+  Qed.
+
+  (* Conversely, if we know that [f] satisfies [P] for at least one point in
+     the search space, then [search_arg] yields some point. *)
+  Lemma search_arg_not_none:
+    forall a b,
+      (exists x, (a <= x < b) /\ P (f x)) ->
+      exists y, search_arg a b = Some y.
+  Proof.
+    move=> a b H_exists.
+    destruct (search_arg a b) eqn:SEARCH; first by exists n.
+    move: SEARCH. rewrite search_arg_none => NOT_exists.
+    exfalso.
+    move: H_exists => [x [RANGE Pfx]].
+    by move: (NOT_exists x RANGE) => /negP not_Pfx.
+  Qed.
+
+  (* Since [search_arg] considers only points at which [f] satisfies [P], if it
+     returns a point, then that point satisfies [P]. *)
+  Lemma search_arg_pred:
+    forall a b x,
+      search_arg a b = Some x -> P (f x).
+  Proof.
+    move=> a b x.
+    elim: b => [| n IND]; first by rewrite /search_arg // ifN.
+    rewrite /search_arg -/search_arg.
+    destruct (a < n.+1) eqn:a_lt_Sn; last by trivial.
+    move: a_lt_Sn. rewrite ltnS => a_lt_Sn.
+    destruct (search_arg a n) as [q|] eqn:REC;
+      destruct (P (f n)) eqn:Pfn => //=;
+      [elim: (R (f n) (f q)) => // |];
+      by move=> x_is; injection x_is => <-.
+  Qed.
+
+  (* Since [search_arg] considers only points within a given range, if it
+     returns a point, then that point lies within the given range. *)
+  Lemma search_arg_in_range:
+    forall a b x,
+      search_arg a b = Some x -> a <= x < b.
+  Proof.
+    move=> a b x.
+    elim: b => [| n IND]; first by rewrite /search_arg // ifN.
+    rewrite /search_arg -/search_arg.
+    destruct (a < n.+1) eqn:a_lt_Sn; last by trivial.
+    move: a_lt_Sn. rewrite ltnS => a_lt_Sn.
+    destruct (search_arg a n) as [q|] eqn:REC;
+      elim: (P (f n)) => //=.
+    - elim: (R (f n) (f q)) => //= x_is;
+        first by injection x_is => <-; apply /andP; split.
+      move: (IND x_is) => /andP [a_le_x x_lt_n].
+      apply /andP; split => //.
+      by rewrite ltnS ltnW.
+    - move => x_is.
+      move: (IND x_is) => /andP [a_le_x x_lt_n].
+      apply /andP; split => //.
+      by rewrite ltnS ltnW.
+    - move => x_is.
+      by injection x_is => <-; apply /andP; split.
+  Qed.
+
+  (* Let us assume that [R] is a reflexive and transitive total order... *)
+  Hypothesis R_reflexive: reflexive R.
+  Hypothesis R_transitive: transitive R.
+  Hypothesis R_total: total R.
+
+  (* ...then [search_arg] yields an extremum w.r.t. to [a, b), that is, if
+     [search_arg] yields a point x, then [R (f x) (f y)] holds for any y in the
+     search range [a, b) that satisfies [P]. *)
+  Lemma search_arg_extremum:
+    forall a b x,
+      search_arg a b = Some x ->
+      forall y,
+        a <= y < b ->
+        P (f y) ->
+        R (f x) (f y).
+  Proof.
+    move=> a b x SEARCH.
+    elim: b x SEARCH => n IND x; first by rewrite /search_arg.
+    rewrite /search_arg -/search_arg.
+    destruct (a < n.+1) eqn:a_lt_Sn; last by trivial.
+    move: a_lt_Sn. rewrite ltnS => a_lt_Sn.
+    destruct (search_arg a n) as [q|] eqn:REC;
+      destruct (P (f n)) eqn:Pfn => //=.
+    - rewrite <- REC in IND.
+      destruct (R (f n) (f q)) eqn:REL => some_x_is;
+        move => y [/andP [a_le_y y_lt_Sn] Pfy];
+        injection some_x_is => x_is; rewrite -{}x_is //;
+        move: y_lt_Sn; rewrite ltnS;
+        rewrite leq_eqVlt => /orP [/eqP EQ | y_lt_n].
+      + by rewrite EQ; apply (R_reflexive (f n)).
+      + apply (R_transitive (f q)) => //.
+        move: (IND q REC y) => HOLDS.
+        apply HOLDS => //.
+        by apply /andP; split.
+      + rewrite EQ.
+        move: (R_total (f q) (f n)) => /orP [R_qn | R_nq] //.
+        by move: REL => /negP.
+      + move: (IND q REC y) => HOLDS.
+        apply HOLDS => //.
+        by apply /andP; split.
+    - move=> some_q_is y [/andP [a_le_y y_lt_Sn] Pfy].
+      move: y_lt_Sn. rewrite ltnS.
+      rewrite leq_eqVlt => /orP [/eqP EQ | y_lt_n].
+      + exfalso. move: Pfn => /negP Pfn. by subst.
+      + apply IND => //. by apply /andP; split.
+    - move=> some_n_is. injection some_n_is => n_is.
+      move=> y [/andP [a_le_y y_lt_Sn] Pfy].
+      move: y_lt_Sn. rewrite ltnS.
+      rewrite leq_eqVlt => /orP [/eqP EQ | y_lt_n].
+      + by rewrite -n_is EQ; apply (R_reflexive (f n)).
+      + exfalso.
+        move: REC. rewrite search_arg_none => NONE.
+        move: (NONE y) => not_Pfy.
+        feed not_Pfy; first by apply /andP; split.
+        by move: not_Pfy => /negP.
+  Qed.
+
+End ArgSearch.
-- 
GitLab