/-
Copyright (c) 2017 Mario Carneiro. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Mario Carneiro
-/
import data.fin.fin2
import data.pfun
import data.vector3
import number_theory.pell

/-!
# Diophantine functions and Matiyasevic's theorem

Hilbert's tenth problem asked whether there exists an algorithm which for a given integer polynomial
determines whether this polynomial has integer solutions. It was answered in the negative in 1970,
the final step being completed by Matiyasevic who showed that the power function is Diophantine.

Here a function is called Diophantine if its graph is Diophantine as a set. A subset `S ⊆ ℕ ^ α` in
turn is called Diophantine if there exists an integer polynomial on `α ⊕ β` such that `v ∈ S` iff
there exists `t : ℕ^β` with `p (v, t) = 0`.

## Main definitions

* `is_poly`: a predicate stating that a function is a multivariate integer polynomial.
* `poly`: the type of multivariate integer polynomial functions.
* `dioph`: a predicate stating that a set `S ⊆ ℕ^α` is Diophantine, i.e. that
* `dioph_fn`: a predicate on a function stating that it is Diophantine in the sense that its graph
  is Diophantine as a set.

## Main statements

* `pell_dioph` states that solutions to Pell's equation form a Diophantine set.
* `pow_dioph` states that the power function is Diophantine, a version of Matiyasevic's theorem.

## References

* [M. Carneiro, _A Lean formalization of Matiyasevic's theorem_][carneiro2018matiyasevic]
* [M. Davis, _Hilbert's tenth problem is unsolvable_][MR317916]

## Tags

Matiyasevic's theorem, Hilbert's tenth problem

## TODO

* Finish the solution of Hilbert's tenth problem.
* Connect `poly` to `mv_polynomial`
-/

universe u

open nat function

namespace int

lemma eq_nat_abs_iff_mul (x n) : nat_abs x = n ↔ (x - n) * (x + n) = 0 :=
begin
  refine iff.trans _ mul_eq_zero.symm,
  refine iff.trans _ (or_congr sub_eq_zero add_eq_zero_iff_eq_neg).symm,
  exact ⟨λe, by rw ← e; apply nat_abs_eq,
    λo, by cases o; subst x; simp [nat_abs_of_nat]⟩
end

end int

open fin2

/-- `list_all p l` is equivalent to `∀ a ∈ l, p a`, but unfolds directly to a conjunction,
  i.e. `list_all p [0, 1, 2] = p 0 ∧ p 1 ∧ p 2`. -/
@[simp] def list_all {α} (p : α → Prop) : list α → Prop
| []        := true
| (x :: []) := p x
| (x :: l)  := p x ∧ list_all l

@[simp] theorem list_all_cons {α} (p : α → Prop) (x : α) :
  ∀ (l : list α), list_all p (x :: l) ↔ p x ∧ list_all p l
| []       := (and_true _).symm
| (x :: l) := iff.rfl

theorem list_all_iff_forall {α} (p : α → Prop) : ∀ (l : list α), list_all p l ↔ ∀ x ∈ l, p x
| []       := (iff_true_intro $ list.ball_nil _).symm
| (x :: l) := by rw [list.ball_cons, ← list_all_iff_forall l]; simp

theorem list_all.imp {α} {p q : α → Prop} (h : ∀ x, p x → q x) :
  ∀ {l : list α}, list_all p l → list_all q l
| []       := id
| (x :: l) := by simpa using and.imp (h x) list_all.imp

@[simp] theorem list_all_map {α β} {p : β → Prop} (f : α → β) {l : list α} :
  list_all p (l.map f) ↔ list_all (p ∘ f) l :=
by induction l; simp *

theorem list_all_congr {α} {p q : α → Prop} (h : ∀ x, p x ↔ q x) {l : list α} :
  list_all p l ↔ list_all q l :=
⟨list_all.imp (λx, (h x).1), list_all.imp (λx, (h x).2)⟩

instance decidable_list_all {α} (p : α → Prop) [decidable_pred p] (l : list α) :
  decidable (list_all p l) :=
decidable_of_decidable_of_iff (by apply_instance) (list_all_iff_forall _ _).symm

/- poly -/

/-- A predicate asserting that a function is a multivariate integer polynomial.
  (We are being a bit lazy here by allowing many representations for multiplication,
  rather than only allowing monomials and addition, but the definition is equivalent
  and this is easier to use.) -/
inductive is_poly {α} : ((α → ℕ) → ℤ) → Prop
| proj : ∀ i, is_poly (λx : α → ℕ, x i)
| const : Π (n : ℤ), is_poly (λx : α → ℕ, n)
| sub : Π {f g : (α → ℕ) → ℤ}, is_poly f → is_poly g → is_poly (λx, f x - g x)
| mul : Π {f g : (α → ℕ) → ℤ}, is_poly f → is_poly g → is_poly (λx, f x * g x)

/-- The type of multivariate integer polynomials -/
def poly (α : Type u) := {f : (α → ℕ) → ℤ // is_poly f}

namespace poly
section
parameter {α : Type u}

instance : has_coe_to_fun (poly α) (λ _, (α → ℕ) → ℤ) := ⟨λ f, f.1⟩

/-- The underlying function of a `poly` is a polynomial -/
lemma isp (f : poly α) : is_poly f := f.2

/-- Extensionality for `poly α` -/
lemma ext {f g : poly α} (e : ∀x, f x = g x) : f = g :=
subtype.eq (funext e)

/-- Construct a `poly` given an extensionally equivalent `poly`. -/
def subst (f : poly α) (g : (α → ℕ) → ℤ) (e : ∀x, f x = g x) : poly α :=
⟨g, by rw ← (funext e : coe_fn f = g); exact f.isp⟩
@[simp] theorem subst_eval (f g e x) : subst f g e x = g x := rfl

/-- The `i`th projection function, `x_i`. -/
def proj (i) : poly α := ⟨_, is_poly.proj i⟩
@[simp] theorem proj_eval (i x) : proj i x = x i := rfl

/-- The constant function with value `n : ℤ`. -/
def const (n) : poly α := ⟨_, is_poly.const n⟩
@[simp] theorem const_eval (n x) : const n x = n := rfl

/-- The zero polynomial -/
def zero : poly α := const 0
instance : has_zero (poly α) := ⟨poly.zero⟩
@[simp] theorem zero_eval (x) : (0 : poly α) x = 0 := rfl

/-- The zero polynomial -/
def one : poly α := const 1
instance : has_one (poly α) := ⟨poly.one⟩
@[simp] theorem one_eval (x) : (1 : poly α) x = 1 := rfl

/-- Subtraction of polynomials -/
def sub : poly α → poly α → poly α | ⟨f, pf⟩ ⟨g, pg⟩ :=
⟨_, is_poly.sub pf pg⟩
instance : has_sub (poly α) := ⟨poly.sub⟩
@[simp] theorem sub_eval : Π (f g x), (f - g : poly α) x = f x - g x
| ⟨f, pf⟩ ⟨g, pg⟩ x := rfl

/-- Negation of a polynomial -/
def neg (f : poly α) : poly α := 0 - f
instance : has_neg (poly α) := ⟨poly.neg⟩
@[simp] theorem neg_eval (f x) : (-f : poly α) x = -f x :=
show (0-f) x = _, by simp

/-- Addition of polynomials -/
def add : poly α → poly α → poly α | ⟨f, pf⟩ ⟨g, pg⟩ :=
subst (⟨f, pf⟩ - -⟨g, pg⟩) _
 (λx, show f x - (0 - g x) = f x + g x, by simp)
instance : has_add (poly α) := ⟨poly.add⟩
@[simp] theorem add_eval : Π (f g x), (f + g : poly α) x = f x + g x
| ⟨f, pf⟩ ⟨g, pg⟩ x := rfl

/-- Multiplication of polynomials -/
def mul : poly α → poly α → poly α | ⟨f, pf⟩ ⟨g, pg⟩ :=
⟨_, is_poly.mul pf pg⟩
instance : has_mul (poly α) := ⟨poly.mul⟩
@[simp] theorem mul_eval : Π (f g x), (f * g : poly α) x = f x * g x
| ⟨f, pf⟩ ⟨g, pg⟩ x := rfl

instance : comm_ring (poly α) := by refine_struct
{ add   := ((+) : poly α → poly α → poly α),
  zero  := 0,
  neg   := (has_neg.neg : poly α → poly α),
  mul   := ((*)),
  one   := 1,
  sub   := (has_sub.sub),
  npow  := @npow_rec _ ⟨(1 : poly α)⟩ ⟨(*)⟩,
  nsmul := @nsmul_rec _ ⟨(0 : poly α)⟩ ⟨(+)⟩,
  zsmul := @zsmul_rec _ ⟨(0 : poly α)⟩ ⟨(+)⟩ ⟨neg⟩ };
intros; try { refl }; refine ext (λ _, _);
simp [sub_eq_add_neg, mul_add, mul_left_comm, mul_comm, add_comm, add_assoc]

lemma induction {C : poly α → Prop}
  (H1 : ∀i, C (proj i)) (H2 : ∀n, C (const n))
  (H3 : ∀f g, C f → C g → C (f - g))
  (H4 : ∀f g, C f → C g → C (f * g)) (f : poly α) : C f :=
begin
  cases f with f pf,
  induction pf with i n f g pf pg ihf ihg f g pf pg ihf ihg,
  apply H1, apply H2, apply H3 _ _ ihf ihg, apply H4 _ _ ihf ihg
end

/-- The sum of squares of a list of polynomials. This is relevant for
  Diophantine equations, because it means that a list of equations
  can be encoded as a single equation: `x = 0 ∧ y = 0 ∧ z = 0` is
  equivalent to `x^2 + y^2 + z^2 = 0`. -/
def sumsq : list (poly α) → poly α
| []      := 0
| (p::ps) := p*p + sumsq ps

theorem sumsq_nonneg (x) : ∀ l, 0 ≤ sumsq l x
| []      := le_refl 0
| (p::ps) := by rw sumsq; simp [-add_comm];
                exact add_nonneg (mul_self_nonneg _) (sumsq_nonneg ps)

theorem sumsq_eq_zero (x) : ∀ l, sumsq l x = 0 ↔ list_all (λa : poly α, a x = 0) l
| []      := eq_self_iff_true _
| (p::ps) := by rw [list_all_cons, ← sumsq_eq_zero ps]; rw sumsq; simp [-add_comm]; exact
  ⟨λ(h : p x * p x + sumsq ps x = 0),
   have p x = 0, from eq_zero_of_mul_self_eq_zero $ le_antisymm
     (by rw ← h; have t := add_le_add_left (sumsq_nonneg x ps) (p x * p x); rwa [add_zero] at t)
     (mul_self_nonneg _),
   ⟨this, by simp [this] at h; exact h⟩,
  λ⟨h1, h2⟩, by rw [h1, h2]; refl⟩

end

/-- Map the index set of variables, replacing `x_i` with `x_(f i)`. -/
def remap {α β} (f : α → β) (g : poly α) : poly β :=
⟨λv, g $ v ∘ f, g.induction
  (λi, by simp; apply is_poly.proj)
  (λn, by simp; apply is_poly.const)
  (λf g pf pg, by simp; apply is_poly.sub pf pg)
  (λf g pf pg, by simp; apply is_poly.mul pf pg)⟩
@[simp] theorem remap_eval {α β} (f : α → β) (g : poly α) (v) : remap f g v = g (v ∘ f) := rfl

end poly

namespace sum

/-- combine two functions into a function on the disjoint union -/
def join {α β γ} (f : α → γ) (g : β → γ) : α ⊕ β → γ :=
by {refine sum.rec _ _, exacts [f, g]}

end sum

local infixr ` ⊗ `:65 := sum.join

open sum

namespace option

/-- Functions from `option` can be combined similarly to `vector.cons` -/
def cons {α β} (a : β) (v : α → β) : option α → β :=
by {refine option.rec _ _, exacts [a, v]}

notation a :: b := cons a b

@[simp] theorem cons_head_tail {α β} (v : option α → β) : v none :: v ∘ some = v :=
funext $ λo, by cases o; refl
end option

/- dioph -/

/-- A set `S ⊆ ℕ^α` is Diophantine if there exists a polynomial on
  `α ⊕ β` such that `v ∈ S` iff there exists `t : ℕ^β` with `p (v, t) = 0`. -/
def dioph {α : Type u} (S : set (α → ℕ)) : Prop :=
∃ {β : Type u} (p : poly (α ⊕ β)), ∀ (v : α → ℕ), S v ↔ ∃t, p (v ⊗ t) = 0

namespace dioph
section
variables {α β γ : Type u}
theorem ext {S S' : set (α → ℕ)} (d : dioph S) (H : ∀v, S v ↔ S' v) : dioph S' :=
eq.rec d $ show S = S', from set.ext H

theorem of_no_dummies (S : set (α → ℕ)) (p : poly α) (h : ∀ (v : α → ℕ), S v ↔ p v = 0) :
  dioph S :=
⟨ulift empty, p.remap inl, λv, (h v).trans
  ⟨λh, ⟨λt, empty.rec _ t.down, by simp; rw [
    show (v ⊗ λt:ulift empty, empty.rec _ t.down) ∘ inl = v, from rfl, h]⟩,
  λ⟨t, ht⟩, by simp at ht; rwa [show (v ⊗ t) ∘ inl = v, from rfl] at ht⟩⟩

lemma inject_dummies_lem (f : β → γ) (g : γ → option β) (inv : ∀ x, g (f x) = some x)
  (p : poly (α ⊕ β)) (v : α → ℕ) : (∃t, p (v ⊗ t) = 0) ↔
    (∃t, p.remap (inl ⊗ (inr ∘ f)) (v ⊗ t) = 0) :=
begin
  simp, refine ⟨λt, _, λt, _⟩; cases t with t ht,
  { have : (v ⊗ (0 :: t) ∘ g) ∘ (inl ⊗ inr ∘ f) = v ⊗ t :=
    funext (λs, by cases s with a b; dsimp [join, (∘)]; try {rw inv}; refl),
    exact ⟨(0 :: t) ∘ g, by rwa this⟩ },
  { have : v ⊗ t ∘ f = (v ⊗ t) ∘ (inl ⊗ inr ∘ f) :=
    funext (λs, by cases s with a b; refl),
    exact ⟨t ∘ f, by rwa this⟩ }
end

theorem inject_dummies {S : set (α → ℕ)} (f : β → γ) (g : γ → option β)
  (inv : ∀ x, g (f x) = some x) (p : poly (α ⊕ β)) (h : ∀ (v : α → ℕ), S v ↔ ∃t, p (v ⊗ t) = 0) :
  ∃ q : poly (α ⊕ γ), ∀ (v : α → ℕ), S v ↔ ∃t, q (v ⊗ t) = 0 :=
⟨p.remap (inl ⊗ (inr ∘ f)), λv, (h v).trans $ inject_dummies_lem f g inv _ _⟩

theorem reindex_dioph {S : set (α → ℕ)} : Π (d : dioph S) (f : α → β), dioph (λv, S (v ∘ f))
| ⟨γ, p, pe⟩ f := ⟨γ, p.remap ((inl ∘ f) ⊗ inr), λv, (pe _).trans $ exists_congr $ λt,
  suffices v ∘ f ⊗ t = (v ⊗ t) ∘ (inl ∘ f ⊗ inr), by simp [this],
  funext $ λs, by cases s with a b; refl⟩

theorem dioph_list_all (l) (d : list_all dioph l) :
  dioph (λv, list_all (λS : set (α → ℕ), S v) l) :=
suffices ∃ β (pl : list (poly (α ⊕ β))), ∀ v,
  list_all (λS : set _, S v) l ↔ ∃t, list_all (λp : poly (α ⊕ β), p (v ⊗ t) = 0) pl,
from let ⟨β, pl, h⟩ := this
  in ⟨β, poly.sumsq pl, λv, (h v).trans $ exists_congr $ λt, (poly.sumsq_eq_zero _ _).symm⟩,
begin
  induction l with S l IH,
  exact ⟨ulift empty, [], λv, by simp; exact ⟨λ⟨t⟩, empty.rec _ t, trivial⟩⟩,
  simp at d,
  exact let ⟨⟨β, p, pe⟩, dl⟩ := d, ⟨γ, pl, ple⟩ := IH dl in
  ⟨β ⊕ γ, p.remap (inl ⊗ inr ∘ inl) :: pl.map (λq, q.remap (inl ⊗ (inr ∘ inr))), λv,
    by simp; exact iff.trans (and_congr (pe v) (ple v))
    ⟨λ⟨⟨m, hm⟩, ⟨n, hn⟩⟩,
      ⟨m ⊗ n, by rw [
        show (v ⊗ m ⊗ n) ∘ (inl ⊗ inr ∘ inl) = v ⊗ m,
        from funext $ λs, by cases s with a b; refl]; exact hm,
      by { refine list_all.imp (λq hq, _) hn, dsimp [(∘)],
           rw [show (λ (x : α ⊕ γ), (v ⊗ m ⊗ n) ((inl ⊗ λ (x : γ), inr (inr x)) x)) = v ⊗ n,
               from funext $ λs, by cases s with a b; refl]; exact hq }⟩,
    λ⟨t, hl, hr⟩,
      ⟨⟨t ∘ inl, by rwa [
        show (v ⊗ t) ∘ (inl ⊗ inr ∘ inl) = v ⊗ t ∘ inl,
        from funext $ λs, by cases s with a b; refl] at hl⟩,
      ⟨t ∘ inr, by
      { refine list_all.imp (λq hq, _) hr, dsimp [(∘)] at hq,
        rwa [show (λ (x : α ⊕ γ), (v ⊗ t) ((inl ⊗ λ (x : γ), inr (inr x)) x)) = v ⊗ t ∘ inr,
             from funext $ λs, by cases s with a b; refl] at hq }⟩⟩⟩⟩
end

theorem and_dioph {S S' : set (α → ℕ)} (d : dioph S) (d' : dioph S') : dioph (λv, S v ∧ S' v) :=
dioph_list_all [S, S'] ⟨d, d'⟩

theorem or_dioph {S S' : set (α → ℕ)} : ∀ (d : dioph S) (d' : dioph S'), dioph (λv, S v ∨ S' v)
| ⟨β, p, pe⟩ ⟨γ, q, qe⟩ := ⟨β ⊕ γ, p.remap (inl ⊗ inr ∘ inl) * q.remap (inl ⊗ inr ∘ inr), λv,
  begin
    refine iff.trans (or_congr ((pe v).trans _) ((qe v).trans _))
      (exists_or_distrib.symm.trans (exists_congr $ λt,
      (@mul_eq_zero _ _ _ (p ((v ⊗ t) ∘ (inl ⊗ inr ∘ inl)))
        (q ((v ⊗ t) ∘ (inl ⊗ inr ∘ inr)))).symm)),
    exact inject_dummies_lem _ (some ⊗ (λ_, none)) (λx, rfl) _ _,
    exact inject_dummies_lem _ ((λ_, none) ⊗ some) (λx, rfl) _ _,
  end⟩

/-- A partial function is Diophantine if its graph is Diophantine. -/
def dioph_pfun (f : (α → ℕ) →. ℕ) := dioph (λv : option α → ℕ, f.graph (v ∘ some, v none))

/-- A function is Diophantine if its graph is Diophantine. -/
def dioph_fn (f : (α → ℕ) → ℕ) := dioph (λv : option α → ℕ, f (v ∘ some) = v none)

theorem reindex_dioph_fn {f : (α → ℕ) → ℕ} (d : dioph_fn f) (g : α → β) :
  dioph_fn (λv, f (v ∘ g)) :=
reindex_dioph d (functor.map g)

theorem ex_dioph {S : set (α ⊕ β → ℕ)} : dioph S → dioph (λv, ∃x, S (v ⊗ x))
| ⟨γ, p, pe⟩ := ⟨β ⊕ γ, p.remap ((inl ⊗ inr ∘ inl) ⊗ inr ∘ inr), λv,
  ⟨λ⟨x, hx⟩, let ⟨t, ht⟩ := (pe _).1 hx in ⟨x ⊗ t, by simp; rw [
    show (v ⊗ x ⊗ t) ∘ ((inl ⊗ inr ∘ inl) ⊗ inr ∘ inr) = (v ⊗ x) ⊗ t,
    from funext $ λs, by cases s with a b; try {cases a}; refl]; exact ht⟩,
  λ⟨t, ht⟩, ⟨t ∘ inl, (pe _).2 ⟨t ∘ inr, by simp at ht; rwa [
    show (v ⊗ t) ∘ ((inl ⊗ inr ∘ inl) ⊗ inr ∘ inr) = (v ⊗ t ∘ inl) ⊗ t ∘ inr,
    from funext $ λs, by cases s with a b; try {cases a}; refl] at ht⟩⟩⟩⟩

theorem ex1_dioph {S : set (option α → ℕ)} : dioph S → dioph (λv, ∃x, S (x :: v))
| ⟨β, p, pe⟩ := ⟨option β, p.remap (inr none :: inl ⊗ inr ∘ some), λv,
  ⟨λ⟨x, hx⟩, let ⟨t, ht⟩ := (pe _).1 hx in ⟨x :: t, by simp; rw [
    show (v ⊗ x :: t) ∘ (inr none :: inl ⊗ inr ∘ some) = x :: v ⊗ t,
    from funext $ λs, by cases s with a b; try {cases a}; refl]; exact ht⟩,
  λ⟨t, ht⟩, ⟨t none, (pe _).2 ⟨t ∘ some, by simp at ht; rwa [
    show (v ⊗ t) ∘ (inr none :: inl ⊗ inr ∘ some) = t none :: v ⊗ t ∘ some,
    from funext $ λs, by cases s with a b; try {cases a}; refl] at ht⟩⟩⟩⟩

theorem dom_dioph {f : (α → ℕ) →. ℕ} (d : dioph_pfun f) : dioph f.dom :=
cast (congr_arg dioph $ set.ext $ λv, (pfun.dom_iff_graph _ _).symm) (ex1_dioph d)

theorem dioph_fn_iff_pfun (f : (α → ℕ) → ℕ) : dioph_fn f = @dioph_pfun α f :=
by refine congr_arg dioph (set.ext $ λv, _); exact pfun.lift_graph.symm

theorem abs_poly_dioph (p : poly α) : dioph_fn (λv, (p v).nat_abs) :=
by refine of_no_dummies _ ((p.remap some - poly.proj none) * (p.remap some + poly.proj none))
  (λv, _); apply int.eq_nat_abs_iff_mul

theorem proj_dioph (i : α) : dioph_fn (λv, v i) :=
abs_poly_dioph (poly.proj i)

theorem dioph_pfun_comp1 {S : set (option α → ℕ)} (d : dioph S) {f} (df : dioph_pfun f) :
  dioph (λv : α → ℕ, ∃ h : f.dom v, S (f.fn v h :: v)) :=
ext (ex1_dioph (and_dioph d df)) $ λv,
⟨λ⟨x, hS, (h: Exists _)⟩, by
  rw [show (x :: v) ∘ some = v, from funext $ λs, rfl] at h;
  cases h with hf h; refine ⟨hf, _⟩; rw [pfun.fn, h]; exact hS,
λ⟨x, hS⟩, ⟨f.fn v x, hS, show Exists _,
  by rw [show (f.fn v x :: v) ∘ some = v, from funext $ λs, rfl]; exact ⟨x, rfl⟩⟩⟩

theorem dioph_fn_comp1 {S : set (option α → ℕ)} (d : dioph S) {f : (α → ℕ) → ℕ} (df : dioph_fn f) :
  dioph (λv : α → ℕ, S (f v :: v)) :=
ext (dioph_pfun_comp1 d (cast (dioph_fn_iff_pfun f) df)) $ λv,
⟨λ⟨_, h⟩, h, λh, ⟨trivial, h⟩⟩

end

section
variables {α β γ : Type}
open vector3
open_locale vector3
theorem dioph_fn_vec_comp1 {n} {S : set (vector3 ℕ (succ n))} (d : dioph S) {f : (vector3 ℕ n) → ℕ}
  (df : dioph_fn f) :
  dioph (λv : vector3 ℕ n, S (cons (f v) v)) :=
ext (dioph_fn_comp1 (reindex_dioph d (none :: some)) df) $ λv, by rw [
  show option.cons (f v) v ∘ (cons none some) = f v :: v,
  from funext $ λs, by cases s with a b; refl]

theorem vec_ex1_dioph (n) {S : set (vector3 ℕ (succ n))} (d : dioph S) :
  dioph (λv : vector3 ℕ n, ∃x, S (x :: v)) :=
ext (ex1_dioph $ reindex_dioph d (none :: some)) $ λv, exists_congr $ λx, by rw [
  show (option.cons x v) ∘ (cons none some) = x :: v,
  from funext $ λs, by cases s with a b; refl]

theorem dioph_fn_vec {n} (f : vector3 ℕ n → ℕ) :
  dioph_fn f ↔ dioph (λv : vector3 ℕ (succ n), f (v ∘ fs) = v fz) :=
⟨λh, reindex_dioph h (fz :: fs), λh, reindex_dioph h (none :: some)⟩

theorem dioph_pfun_vec {n} (f : vector3 ℕ n →. ℕ) :
  dioph_pfun f ↔ dioph (λv : vector3 ℕ (succ n), f.graph (v ∘ fs, v fz)) :=
⟨λh, reindex_dioph h (fz :: fs), λh, reindex_dioph h (none :: some)⟩

theorem dioph_fn_compn {α : Type} : ∀ {n} {S : set (α ⊕ fin2 n → ℕ)} (d : dioph S)
  {f : vector3 ((α → ℕ) → ℕ) n} (df : vector_allp dioph_fn f),
  dioph (λv : α → ℕ, S (v ⊗ λi, f i v))
| 0 S d f := λdf, ext (reindex_dioph d (id ⊗ fin2.elim0)) $ λv,
  by refine eq.to_iff (congr_arg S $ funext $ λs, _); {cases s with a b, refl, cases b}
| (succ n) S d f := f.cons_elim $ λf fl, by simp; exact λ df dfl,
  have dioph (λv, S (v ∘ inl ⊗ f (v ∘ inl) :: v ∘ inr)),
  from ext (dioph_fn_comp1 (reindex_dioph d (some ∘ inl ⊗ none :: some ∘ inr))
    (reindex_dioph_fn df inl)) $
    λv, by {refine eq.to_iff (congr_arg S $ funext $ λs, _); cases s with a b, refl, cases b; refl},
  have dioph (λv, S (v ⊗ f v :: λ (i : fin2 n), fl i v)),
  from @dioph_fn_compn n (λv, S (v ∘ inl ⊗ f (v ∘ inl) :: v ∘ inr)) this _ dfl,
  ext this $ λv, by rw [
    show cons (f v) (λ (i : fin2 n), fl i v) = λ (i : fin2 (succ n)), (f :: fl) i v,
    from funext $ λs, by cases s with a b; refl]

theorem dioph_comp {n} {S : set (vector3 ℕ n)} (d : dioph S)
  (f : vector3 ((α → ℕ) → ℕ) n) (df : vector_allp dioph_fn f) : dioph (λv, S (λi, f i v)) :=
dioph_fn_compn (reindex_dioph d inr) df

theorem dioph_fn_comp {n} {f : vector3 ℕ n → ℕ} (df : dioph_fn f)
  (g : vector3 ((α → ℕ) → ℕ) n) (dg : vector_allp dioph_fn g) : dioph_fn (λv, f (λi, g i v)) :=
dioph_comp ((dioph_fn_vec _).1 df) ((λv, v none) :: λi v, g i (v ∘ some)) $
by simp; exact ⟨proj_dioph none, (vector_allp_iff_forall _ _).2 $ λi,
  reindex_dioph_fn ((vector_allp_iff_forall _ _).1 dg _) _⟩

localized "notation x ` D∧ `:35 y := dioph.and_dioph x y" in dioph
localized "notation x ` D∨ `:35 y := dioph.or_dioph x y" in dioph

localized "notation `D∃`:30 := dioph.vec_ex1_dioph" in dioph

localized "prefix `&`:max := of_nat'" in dioph
theorem proj_dioph_of_nat {n : ℕ} (m : ℕ) [is_lt m n] : dioph_fn (λv : vector3 ℕ n, v &m) :=
proj_dioph &m
localized "prefix `D&`:100 := dioph.proj_dioph_of_nat" in dioph

theorem const_dioph (n : ℕ) : dioph_fn (const (α → ℕ) n) :=
abs_poly_dioph (poly.const n)
localized "prefix `D.`:100 := dioph.const_dioph" in dioph

variables {f g : (α → ℕ) → ℕ} (df : dioph_fn f) (dg : dioph_fn g)
include df dg

theorem dioph_comp2 {S : ℕ → ℕ → Prop} (d : dioph (λv:vector3 ℕ 2, S (v &0) (v &1))) :
  dioph (λv, S (f v) (g v)) :=
dioph_comp d [f, g] (by exact ⟨df, dg⟩)

theorem dioph_fn_comp2 {h : ℕ → ℕ → ℕ} (d : dioph_fn (λv:vector3 ℕ 2, h (v &0) (v &1))) :
  dioph_fn (λv, h (f v) (g v)) :=
dioph_fn_comp d [f, g] (by exact ⟨df, dg⟩)

theorem eq_dioph : dioph (λv, f v = g v) :=
dioph_comp2 df dg $ of_no_dummies _ (poly.proj &0 - poly.proj &1)
  (λv, (int.coe_nat_eq_coe_nat_iff _ _).symm.trans
  ⟨@sub_eq_zero_of_eq ℤ _ (v &0) (v &1), eq_of_sub_eq_zero⟩)
localized "infix ` D= `:50 := dioph.eq_dioph" in dioph

theorem add_dioph : dioph_fn (λv, f v + g v) :=
dioph_fn_comp2 df dg $ abs_poly_dioph (poly.proj &0 + poly.proj &1)
localized "infix ` D+ `:80 := dioph.add_dioph" in dioph

theorem mul_dioph : dioph_fn (λv, f v * g v) :=
dioph_fn_comp2 df dg $ abs_poly_dioph (poly.proj &0 * poly.proj &1)
localized "infix ` D* `:90 := dioph.mul_dioph" in dioph

theorem le_dioph : dioph (λv, f v ≤ g v) :=
dioph_comp2 df dg $ ext (D∃2 $ D&1 D+ D&0 D= D&2) (λv, ⟨λ⟨x, hx⟩, le.intro hx, le.dest⟩)
localized "infix ` D≤ `:50 := dioph.le_dioph" in dioph

theorem lt_dioph : dioph (λv, f v < g v) := df D+ (D. 1) D≤ dg
localized "infix ` D< `:50 := dioph.lt_dioph" in dioph

theorem ne_dioph : dioph (λv, f v ≠ g v) :=
ext (df D< dg D∨ dg D< df) $ λv, ne_iff_lt_or_gt.symm
localized "infix ` D≠ `:50 := dioph.ne_dioph" in dioph

theorem sub_dioph : dioph_fn (λv, f v - g v) :=
dioph_fn_comp2 df dg $ (dioph_fn_vec _).2 $
ext (D&1 D= D&0 D+ D&2 D∨ D&1 D≤ D&2 D∧ D&0 D= D.0) $ (vector_all_iff_forall _).1 $ λx y z,
show (y = x + z ∨ y ≤ z ∧ x = 0) ↔ y - z = x, from
⟨λo, begin
  rcases o with ae | ⟨yz, x0⟩,
  { rw [ae, add_tsub_cancel_right] },
  { rw [x0, tsub_eq_zero_iff_le.mpr yz] }
end, λh, begin
  subst x,
  cases le_total y z with yz zy,
  { exact or.inr ⟨yz, tsub_eq_zero_iff_le.mpr yz⟩ },
  { exact or.inl (tsub_add_cancel_of_le zy).symm },
end⟩
localized "infix ` D- `:80 := dioph.sub_dioph" in dioph

theorem dvd_dioph : dioph (λv, f v ∣ g v) :=
dioph_comp (D∃2 $ D&2 D= D&1 D* D&0) [f, g] (by exact ⟨df, dg⟩)
localized "infix ` D∣ `:50 := dioph.dvd_dioph" in dioph

theorem mod_dioph : dioph_fn (λv, f v % g v) :=
have dioph (λv : vector3 ℕ 3, (v &2 = 0 ∨ v &0 < v &2) ∧ ∃ (x : ℕ), v &0 + v &2 * x = v &1),
from (D&2 D= D.0 D∨ D&0 D< D&2) D∧ (D∃3 $ D&1 D+ D&3 D* D&0 D= D&2),
dioph_fn_comp2 df dg $ (dioph_fn_vec _).2 $ ext this $ (vector_all_iff_forall _).1 $ λz x y,
show ((y = 0 ∨ z < y) ∧ ∃ c, z + y * c = x) ↔ x % y = z, from
⟨λ⟨h, c, hc⟩, begin rw ← hc; simp; cases h with x0 hl, rw [x0, mod_zero], exact mod_eq_of_lt hl end,
λe, by rw ← e; exact ⟨or_iff_not_imp_left.2 $ λh, mod_lt _ (nat.pos_of_ne_zero h), x / y,
  mod_add_div _ _⟩⟩

localized "infix ` D% `:80 := dioph.mod_dioph" in dioph

theorem modeq_dioph {h : (α → ℕ) → ℕ} (dh : dioph_fn h) : dioph (λv, f v ≡ g v [MOD h v]) :=
df D% dh D= dg D% dh
localized "notation `D≡` := dioph.modeq_dioph" in dioph

theorem div_dioph : dioph_fn (λv, f v / g v) :=
have dioph (λv : vector3 ℕ 3, v &2 = 0 ∧ v &0 = 0 ∨ v &0 * v &2 ≤ v &1 ∧ v &1 < (v &0 + 1) * v &2),
from (D&2 D= D.0 D∧ D&0 D= D.0) D∨ D&0 D* D&2 D≤ D&1 D∧ D&1 D< (D&0 D+ D.1) D* D&2,
dioph_fn_comp2 df dg $ (dioph_fn_vec _).2 $ ext this $ (vector_all_iff_forall _).1 $ λz x y,
show y = 0 ∧ z = 0 ∨ z * y ≤ x ∧ x < (z + 1) * y ↔ x / y = z,
by refine iff.trans _ eq_comm; exact y.eq_zero_or_pos.elim
  (λy0, by rw [y0, nat.div_zero]; exact
    ⟨λo, (o.resolve_right $ λ⟨_, h2⟩, nat.not_lt_zero _ h2).right, λz0, or.inl ⟨rfl, z0⟩⟩)
  (λypos, iff.trans ⟨λo, o.resolve_left $ λ⟨h1, _⟩, ne_of_gt ypos h1, or.inr⟩
    (le_antisymm_iff.trans $ and_congr (nat.le_div_iff_mul_le _ _ ypos) $
      iff.trans ⟨lt_succ_of_le, le_of_lt_succ⟩ (div_lt_iff_lt_mul _ _ ypos)).symm)
localized "infix ` D/ `:80 := dioph.div_dioph" in dioph

omit df dg
open pell

theorem pell_dioph : dioph (λv:vector3 ℕ 4, ∃ h : 1 < v &0,
  xn h (v &1) = v &2 ∧ yn h (v &1) = v &3) :=
have dioph {v : vector3 ℕ 4 |
1 < v &0 ∧ v &1 ≤ v &3 ∧
(v &2 = 1 ∧ v &3 = 0 ∨
∃ (u w s t b : ℕ),
  v &2 * v &2 - (v &0 * v &0 - 1) * v &3 * v &3 = 1 ∧
  u * u - (v &0 * v &0 - 1) * w * w = 1 ∧
  s * s - (b * b - 1) * t * t = 1 ∧
  1 < b ∧ (b ≡ 1 [MOD 4 * v &3]) ∧ (b ≡ v &0 [MOD u]) ∧
  0 < w ∧ v &3 * v &3 ∣ w ∧
  (s ≡ v &2 [MOD u]) ∧
  (t ≡ v &1 [MOD 4 * v &3]))}, from
D.1 D< D&0 D∧ D&1 D≤ D&3 D∧
((D&2 D= D.1 D∧ D&3 D= D.0) D∨
 (D∃4 $ D∃5 $ D∃6 $ D∃7 $ D∃8 $
  D&7 D* D&7 D- (D&5 D* D&5 D- D.1) D* D&8 D* D&8 D= D.1 D∧
  D&4 D* D&4 D- (D&5 D* D&5 D- D.1) D* D&3 D* D&3 D= D.1 D∧
  D&2 D* D&2 D- (D&0 D* D&0 D- D.1) D* D&1 D* D&1 D= D.1 D∧
  D.1 D< D&0 D∧ (D≡ (D&0) (D.1) (D.4 D* D&8)) D∧ (D≡ (D&0) (D&5) D&4) D∧
  D.0 D< D&3 D∧ D&8 D* D&8 D∣ D&3 D∧
  (D≡ (D&2) (D&7) D&4) D∧
  (D≡ (D&1) (D&6) (D.4 D* D&8)))),
dioph.ext this $ λv, matiyasevic.symm

theorem xn_dioph : dioph_pfun (λv:vector3 ℕ 2, ⟨1 < v &0, λh, xn h (v &1)⟩) :=
have dioph (λv:vector3 ℕ 3, ∃ y, ∃ h : 1 < v &1, xn h (v &2) = v &0 ∧ yn h (v &2) = y), from
let D_pell := @reindex_dioph _ (fin2 4) _ pell_dioph [&2, &3, &1, &0] in D∃3 D_pell,
(dioph_pfun_vec _).2 $ dioph.ext this $ λv, ⟨λ⟨y, h, xe, ye⟩, ⟨h, xe⟩, λ⟨h, xe⟩, ⟨_, h, xe, rfl⟩⟩

include df dg
/-- A version of **Matiyasevic's theorem** -/
theorem pow_dioph : dioph_fn (λv, f v ^ g v) :=
have dioph {v : vector3 ℕ 3 |
v &2 = 0 ∧ v &0 = 1 ∨ 0 < v &2 ∧
(v &1 = 0 ∧ v &0 = 0 ∨ 0 < v &1 ∧
∃ (w a t z x y : ℕ),
  (∃ (a1 : 1 < a), xn a1 (v &2) = x ∧ yn a1 (v &2) = y) ∧
  (x ≡ y * (a - v &1) + v &0 [MOD t]) ∧
  2 * a * v &1 = t + (v &1 * v &1 + 1) ∧
  v &0 < t ∧ v &1 ≤ w ∧ v &2 ≤ w ∧
  a * a - ((w + 1) * (w + 1) - 1) * (w * z) * (w * z) = 1)}, from
let D_pell := @reindex_dioph _ (fin2 9) _ pell_dioph [&4, &8, &1, &0] in
(D&2 D= D.0 D∧ D&0 D= D.1) D∨ (D.0 D< D&2 D∧
((D&1 D= D.0 D∧ D&0 D= D.0) D∨ (D.0 D< D&1 D∧
 (D∃3 $ D∃4 $ D∃5 $ D∃6 $ D∃7 $ D∃8 $ D_pell D∧
  (D≡ (D&1) (D&0 D* (D&4 D- D&7) D+ D&6) (D&3)) D∧
  D.2 D* D&4 D* D&7 D= D&3 D+ (D&7 D* D&7 D+ D.1) D∧
  D&6 D< D&3 D∧ D&7 D≤ D&5 D∧ D&8 D≤ D&5 D∧
  D&4 D* D&4 D- ((D&5 D+ D.1) D* (D&5 D+ D.1) D- D.1) D* (D&5 D* D&2) D* (D&5 D* D&2) D= D.1)))),
dioph_fn_comp2 df dg $ (dioph_fn_vec _).2 $ dioph.ext this $ λv, iff.symm $
eq_pow_of_pell.trans $ or_congr iff.rfl $ and_congr iff.rfl $ or_congr iff.rfl $ and_congr iff.rfl $
⟨λ⟨w, a, t, z, a1, h⟩, ⟨w, a, t, z, _, _, ⟨a1, rfl, rfl⟩, h⟩,
 λ⟨w, a, t, z, ._, ._, ⟨a1, rfl, rfl⟩, h⟩, ⟨w, a, t, z, a1, h⟩⟩

end
end dioph


import Mathlib.Data.List.Basic
import Mathlib.Data.String.Defs

namespace String

lemma congr_append : ∀ (a b : String), a ++ b = String.mk (a.data ++ b.data)
| ⟨_⟩, ⟨_⟩ => rfl

@[simp] lemma length_append : ∀ (as bs : String), (as ++ bs).length = as.length + bs.length
| ⟨as⟩, ⟨bs⟩ => by
  rw [congr_append]
  simp only [String.length]
  exact List.length_append as bs

@[simp] lemma length_replicate (n : ℕ) (c : Char) : (replicate n c).length = n :=
by simp only [String.length, String.replicate, List.length_replicate]

lemma length_eq_list_length (l : List Char) : (String.mk l).length = l.length :=
by simp only [String.length]

/-- The length of the String returned by `String.leftpad n a c` is equal
  to the larger of `n` and `s.length` -/
@[simp] lemma leftpad_length (n : ℕ) (c : Char) :
  ∀ (s : String), (leftpad n c s).length = max n s.length
| ⟨s⟩ => by simp only [leftpad, String.length, List.leftpad_length]

lemma leftpad_prefix (n : ℕ) (c : Char) : ∀ s, isPrefix (replicate (n - length s) c) (leftpad n c s)
| ⟨l⟩ => by simp only [isPrefix, replicate, leftpad, String.length, List.leftpad_prefix]

lemma leftpad_suffix (n : ℕ) (c : Char) : ∀ s, isSuffix s (leftpad n c s)
| ⟨l⟩ => by simp only [isSuffix, replicate, leftpad, String.length, List.leftpad_suffix]

end String


/-
Copyright (c) 2021 Yaël Dillies. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Yaël Dillies
-/
import analysis.convex.function

/-!
# Quasiconvex and quasiconcave functions

This file defines quasiconvexity, quasiconcavity and quasilinearity of functions, which are
generalizations of unimodality and monotonicity. Convexity implies quasiconvexity, concavity implies
quasiconcavity, and monotonicity implies quasilinearity.

## Main declarations

* `quasiconvex_on 𝕜 s f`: Quasiconvexity of the function `f` on the set `s` with scalars `𝕜`. This
  means that, for all `r`, `{x ∈ s | f x ≤ r}` is `𝕜`-convex.
* `quasiconcave_on 𝕜 s f`: Quasiconcavity of the function `f` on the set `s` with scalars `𝕜`. This
  means that, for all `r`, `{x ∈ s | r ≤ f x}` is `𝕜`-convex.
* `quasilinear_on 𝕜 s f`: Quasilinearity of the function `f` on the set `s` with scalars `𝕜`. This
  means that `f` is both quasiconvex and quasiconcave.

## TODO

Prove that a quasilinear function between two linear orders is either monotone or antitone. This is
not hard but quite a pain to go about as there are many cases to consider.

## References

* https://en.wikipedia.org/wiki/Quasiconvex_function
-/

open function set

variables {𝕜 E F β : Type*}

section ordered_semiring
variables [ordered_semiring 𝕜]

section add_comm_monoid
variables [add_comm_monoid E] [add_comm_monoid F]

section ordered_add_comm_monoid
variables (𝕜) [ordered_add_comm_monoid β] [has_scalar 𝕜 E] (s : set E) (f : E → β)

/-- A function is quasiconvex if all its sublevels are convex.
This means that, for all `r`, `{x ∈ s | f x ≤ r}` is `𝕜`-convex. -/
def quasiconvex_on : Prop :=
∀ r, convex 𝕜 {x ∈ s | f x ≤ r}

/-- A function is quasiconcave if all its superlevels are convex.
This means that, for all `r`, `{x ∈ s | r ≤ f x}` is `𝕜`-convex. -/
def quasiconcave_on : Prop :=
∀ r, convex 𝕜 {x ∈ s | r ≤ f x}

/-- A function is quasilinear if it is both quasiconvex and quasiconcave.
This means that, for all `r`,
the sets `{x ∈ s | f x ≤ r}` and `{x ∈ s | r ≤ f x}` are `𝕜`-convex. -/
def quasilinear_on : Prop :=
quasiconvex_on 𝕜 s f ∧ quasiconcave_on 𝕜 s f

variables {𝕜 s f}

lemma quasiconvex_on.dual (hf : quasiconvex_on 𝕜 s f) :
  @quasiconcave_on 𝕜 E (order_dual β) _ _ _ _ s f :=
hf

lemma quasiconcave_on.dual (hf : quasiconcave_on 𝕜 s f) :
  @quasiconvex_on 𝕜 E (order_dual β) _ _ _ _ s f :=
hf

lemma quasilinear_on.dual (hf : quasilinear_on 𝕜 s f) :
  @quasilinear_on 𝕜 E (order_dual β) _ _ _ _ s f :=
⟨hf.2, hf.1⟩

lemma convex.quasiconvex_on_of_convex_le (hs : convex 𝕜 s) (h : ∀ r, convex 𝕜 {x | f x ≤ r}) :
  quasiconvex_on 𝕜 s f :=
λ r, hs.inter (h r)

lemma convex.quasiconcave_on_of_convex_ge (hs : convex 𝕜 s) (h : ∀ r, convex 𝕜 {x | r ≤ f x}) :
  quasiconcave_on 𝕜 s f :=
@convex.quasiconvex_on_of_convex_le 𝕜 E (order_dual β) _ _ _ _ _ _ hs h

lemma quasiconvex_on.convex [is_directed β (≤)] (hf : quasiconvex_on 𝕜 s f) : convex 𝕜 s :=
λ x y hx hy a b ha hb hab,
  let ⟨z, hxz, hyz⟩ := exists_ge_ge (f x) (f y) in (hf _ ⟨hx, hxz⟩ ⟨hy, hyz⟩ ha hb hab).1

lemma quasiconcave_on.convex [is_directed β (swap (≤))] (hf : quasiconcave_on 𝕜 s f) : convex 𝕜 s :=
hf.dual.convex

end ordered_add_comm_monoid

section linear_ordered_add_comm_monoid
variables [linear_ordered_add_comm_monoid β]

section has_scalar
variables [has_scalar 𝕜 E] {s : set E} {f g : E → β}

lemma quasiconvex_on.sup (hf : quasiconvex_on 𝕜 s f) (hg : quasiconvex_on 𝕜 s g) :
  quasiconvex_on 𝕜 s (f ⊔ g) :=
begin
  intro r,
  simp_rw [pi.sup_def, sup_le_iff, ←set.sep_inter_sep],
  exact (hf r).inter (hg r),
end

lemma quasiconcave_on.inf (hf : quasiconcave_on 𝕜 s f) (hg : quasiconcave_on 𝕜 s g) :
  quasiconcave_on 𝕜 s (f ⊓ g) :=
hf.dual.sup hg

lemma quasiconvex_on_iff_le_max :
  quasiconvex_on 𝕜 s f ↔ convex 𝕜 s ∧
    ∀ ⦃x y : E⦄, x ∈ s → y ∈ s → ∀ ⦃a b : 𝕜⦄, 0 ≤ a → 0 ≤ b → a + b = 1 →
      f (a • x + b • y) ≤ max (f x) (f y) :=
⟨λ hf, ⟨hf.convex, λ x y hx hy a b ha hb hab,
  (hf _ ⟨hx, le_max_left _ _⟩ ⟨hy, le_max_right _ _⟩ ha hb hab).2⟩,
  λ hf r x y hx hy a b ha hb hab,
  ⟨hf.1 hx.1 hy.1 ha hb hab, (hf.2 hx.1 hy.1 ha hb hab).trans $ max_le hx.2 hy.2⟩⟩

lemma quasiconcave_on_iff_min_le :
  quasiconcave_on 𝕜 s f ↔ convex 𝕜 s ∧
    ∀ ⦃x y : E⦄, x ∈ s → y ∈ s → ∀ ⦃a b : 𝕜⦄, 0 ≤ a → 0 ≤ b → a + b = 1 →
      min (f x) (f y) ≤ f (a • x + b • y) :=
@quasiconvex_on_iff_le_max 𝕜 E (order_dual β) _ _ _ _ _ _

lemma quasilinear_on_iff_mem_interval :
  quasilinear_on 𝕜 s f ↔ convex 𝕜 s ∧
    ∀ ⦃x y : E⦄, x ∈ s → y ∈ s → ∀ ⦃a b : 𝕜⦄, 0 ≤ a → 0 ≤ b → a + b = 1 →
      f (a • x + b • y) ∈ interval (f x) (f y) :=
begin
  rw [quasilinear_on, quasiconvex_on_iff_le_max, quasiconcave_on_iff_min_le, and_and_and_comm,
    and_self],
  apply and_congr_right',
  simp_rw [←forall_and_distrib, interval, mem_Icc, and_comm],
end

lemma quasiconvex_on.convex_lt (hf : quasiconvex_on 𝕜 s f) (r : β) : convex 𝕜 {x ∈ s | f x < r} :=
begin
  refine λ x y hx hy a b ha hb hab, _,
  have h := hf _ ⟨hx.1, le_max_left _ _⟩ ⟨hy.1, le_max_right _ _⟩ ha hb hab,
  exact ⟨h.1, h.2.trans_lt $ max_lt hx.2 hy.2⟩,
end

lemma quasiconcave_on.convex_gt (hf : quasiconcave_on 𝕜 s f) (r : β) : convex 𝕜 {x ∈ s | r < f x} :=
hf.dual.convex_lt r

end has_scalar

section ordered_smul
variables [has_scalar 𝕜 E] [module 𝕜 β] [ordered_smul 𝕜 β] {s : set E} {f : E → β}

lemma convex_on.quasiconvex_on (hf : convex_on 𝕜 s f) : quasiconvex_on 𝕜 s f :=
hf.convex_le

lemma concave_on.quasiconcave_on (hf : concave_on 𝕜 s f) : quasiconcave_on 𝕜 s f :=
hf.convex_ge

end ordered_smul
end linear_ordered_add_comm_monoid
end add_comm_monoid

section linear_ordered_add_comm_monoid
variables [linear_ordered_add_comm_monoid E] [ordered_add_comm_monoid β] [module 𝕜 E]
  [ordered_smul 𝕜 E] {s : set E} {f : E → β}

lemma monotone_on.quasiconvex_on (hf : monotone_on f s) (hs : convex 𝕜 s) : quasiconvex_on 𝕜 s f :=
hf.convex_le hs

lemma monotone_on.quasiconcave_on (hf : monotone_on f s) (hs : convex 𝕜 s) :
  quasiconcave_on 𝕜 s f :=
hf.convex_ge hs

lemma monotone_on.quasilinear_on (hf : monotone_on f s) (hs : convex 𝕜 s) : quasilinear_on 𝕜 s f :=
⟨hf.quasiconvex_on hs, hf.quasiconcave_on hs⟩

lemma antitone_on.quasiconvex_on (hf : antitone_on f s) (hs : convex 𝕜 s) : quasiconvex_on 𝕜 s f :=
hf.convex_le hs

lemma antitone_on.quasiconcave_on (hf : antitone_on f s) (hs : convex 𝕜 s) :
  quasiconcave_on 𝕜 s f :=
hf.convex_ge hs

lemma antitone_on.quasilinear_on (hf : antitone_on f s) (hs : convex 𝕜 s) : quasilinear_on 𝕜 s f :=
⟨hf.quasiconvex_on hs, hf.quasiconcave_on hs⟩

lemma monotone.quasiconvex_on (hf : monotone f) : quasiconvex_on 𝕜 univ f :=
(hf.monotone_on _).quasiconvex_on convex_univ

lemma monotone.quasiconcave_on (hf : monotone f) : quasiconcave_on 𝕜 univ f :=
(hf.monotone_on _).quasiconcave_on convex_univ

lemma monotone.quasilinear_on (hf : monotone f) : quasilinear_on 𝕜 univ f :=
⟨hf.quasiconvex_on, hf.quasiconcave_on⟩

lemma antitone.quasiconvex_on (hf : antitone f) : quasiconvex_on 𝕜 univ f :=
(hf.antitone_on _).quasiconvex_on convex_univ

lemma antitone.quasiconcave_on (hf : antitone f) : quasiconcave_on 𝕜 univ f :=
(hf.antitone_on _).quasiconcave_on convex_univ

lemma antitone.quasilinear_on (hf : antitone f) : quasilinear_on 𝕜 univ f :=
⟨hf.quasiconvex_on, hf.quasiconcave_on⟩

end linear_ordered_add_comm_monoid
end ordered_semiring


/-
Copyright (c) 2022 Yaël Dillies. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Yaël Dillies

! This file was ported from Lean 3 source module data.finsupp.interval
! leanprover-community/mathlib commit 0a0ec35061ed9960bf0e7ffb0335f44447b58977
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathlib.Data.Finset.Finsupp
import Mathlib.Data.Finset.LocallyFinite
import Mathlib.Data.Finsupp.Order

/-!
# Finite intervals of finitely supported functions

This file provides the `LocallyFiniteOrder` instance for `ι →₀ α` when `α` itself is locally
finite and calculates the cardinality of its finite intervals.

## Main declarations

* `Finsupp.rangeSingleton`: Postcomposition with `Singleton.singleton` on `Finset` as a
  `Finsupp`.
* `Finsupp.rangeIcc`: Postcomposition with `Finset.Icc` as a `Finsupp`.

Both these definitions use the fact that `0 = {0}` to ensure that the resulting function is finitely
supported.
-/

noncomputable section

open Finset Finsupp Function

open BigOperators Classical Pointwise

variable {ι α : Type _}

namespace Finsupp

section RangeSingleton

variable [Zero α] {f : ι →₀ α} {i : ι} {a : α}

/-- Pointwise `Singleton.singleton` bundled as a `Finsupp`. -/
@[simps]
def rangeSingleton (f : ι →₀ α) : ι →₀ Finset α where
  toFun i := {f i}
  support := f.support
  mem_support_toFun i := by
    rw [← not_iff_not, not_mem_support_iff, not_ne_iff]
    exact singleton_injective.eq_iff.symm
#align finsupp.range_singleton Finsupp.rangeSingleton

theorem mem_rangeSingleton_apply_iff : a ∈ f.rangeSingleton i ↔ a = f i :=
  mem_singleton
#align finsupp.mem_range_singleton_apply_iff Finsupp.mem_rangeSingleton_apply_iff

end RangeSingleton

section RangeIcc

variable [Zero α] [PartialOrder α] [LocallyFiniteOrder α] {f g : ι →₀ α} {i : ι} {a : α}

/-- Pointwise `Finset.Icc` bundled as a `Finsupp`. -/
@[simps toFun]
def rangeIcc (f g : ι →₀ α) : ι →₀ Finset α where
  toFun i := Icc (f i) (g i)
  support :=
    -- porting note: Not needed (due to open Classical), in mathlib3 too
    -- haveI := Classical.decEq ι
    f.support ∪ g.support
  mem_support_toFun i := by
    rw [mem_union, ← not_iff_not, not_or, not_mem_support_iff, not_mem_support_iff, not_ne_iff]
    exact Icc_eq_singleton_iff.symm
#align finsupp.range_Icc Finsupp.rangeIcc

-- porting note: Added as alternative to rangeIcc_toFun to be used in proof of card_Icc
lemma coe_rangeIcc (f g : ι →₀ α) : rangeIcc f g i = Icc (f i) (g i) := rfl

@[simp]
theorem rangeIcc_support (f g : ι →₀ α) :
    (rangeIcc f g).support = f.support ∪ g.support := rfl
#align finsupp.range_Icc_support Finsupp.rangeIcc_support

theorem mem_rangeIcc_apply_iff : a ∈ f.rangeIcc g i ↔ f i ≤ a ∧ a ≤ g i := mem_Icc
#align finsupp.mem_range_Icc_apply_iff Finsupp.mem_rangeIcc_apply_iff

end RangeIcc

section PartialOrder

variable [PartialOrder α] [Zero α] [LocallyFiniteOrder α] (f g : ι →₀ α)

instance : LocallyFiniteOrder (ι →₀ α) :=
  -- porting note: Not needed (due to open Classical), in mathlib3 too
  -- haveI := Classical.decEq ι
  -- haveI := Classical.decEq α
  LocallyFiniteOrder.ofIcc (ι →₀ α) (fun f g => (f.support ∪ g.support).finsupp <| f.rangeIcc g)
    fun f g x => by
      refine'
        (mem_finsupp_iff_of_support_subset <| Finset.subset_of_eq <| rangeIcc_support _ _).trans _
      simp_rw [mem_rangeIcc_apply_iff]
      exact forall_and

theorem icc_eq : Icc f g = (f.support ∪ g.support).finsupp (f.rangeIcc g) := rfl
#align finsupp.Icc_eq Finsupp.icc_eq

-- porting note: removed [DecidableEq ι]
theorem card_Icc : (Icc f g).card = ∏ i in f.support ∪ g.support, (Icc (f i) (g i)).card := by
  simp_rw [icc_eq, card_finsupp, coe_rangeIcc]

#align finsupp.card_Icc Finsupp.card_Icc

-- porting note: removed [DecidableEq ι]
theorem card_Ico : (Ico f g).card = (∏ i in f.support ∪ g.support, (Icc (f i) (g i)).card) - 1 := by
  rw [card_Ico_eq_card_Icc_sub_one, card_Icc]
#align finsupp.card_Ico Finsupp.card_Ico

-- porting note: removed [DecidableEq ι]
theorem card_Ioc : (Ioc f g).card = (∏ i in f.support ∪ g.support, (Icc (f i) (g i)).card) - 1 := by
  rw [card_Ioc_eq_card_Icc_sub_one, card_Icc]
#align finsupp.card_Ioc Finsupp.card_Ioc

-- porting note: removed [DecidableEq ι]
theorem card_Ioo : (Ioo f g).card = (∏ i in f.support ∪ g.support, (Icc (f i) (g i)).card) - 2 := by
  rw [card_Ioo_eq_card_Icc_sub_two, card_Icc]
#align finsupp.card_Ioo Finsupp.card_Ioo

end PartialOrder

section CanonicallyOrdered

variable [CanonicallyOrderedAddMonoid α] [LocallyFiniteOrder α]

variable (f : ι →₀ α)

theorem card_Iic : (Iic f).card = ∏ i in f.support, (Iic (f i)).card := by
  classical simp_rw [Iic_eq_Icc, card_Icc, Finsupp.bot_eq_zero, support_zero, empty_union,
      zero_apply, bot_eq_zero]
#align finsupp.card_Iic Finsupp.card_Iic

theorem card_Iio : (Iio f).card = (∏ i in f.support, (Iic (f i)).card) - 1 := by
  rw [card_Iio_eq_card_Iic_sub_one, card_Iic]
#align finsupp.card_Iio Finsupp.card_Iio

end CanonicallyOrdered

end Finsupp


/-
Copyright (c) 2019 Johannes Hölzl. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Johannes Hölzl
-/

import ring_theory.mv_polynomial.basic

/-!
# Multivariate polynomials over fields

This file contains basic facts about multivariate polynomials over fields, for example that the
dimension of the space of multivariate polynomials over a field is equal to the cardinality of
finitely supported functions from the indexing set to `ℕ`.
-/

noncomputable theory

open_locale classical

open set linear_map submodule
open_locale big_operators

namespace mv_polynomial
universes u v
variables {σ : Type u} {K : Type v}
variables (σ K) [field K]

lemma quotient_mk_comp_C_injective (I : ideal (mv_polynomial σ K)) (hI : I ≠ ⊤) :
  function.injective ((ideal.quotient.mk I).comp mv_polynomial.C) :=
begin
  refine (ring_hom.injective_iff _).2 (λ x hx, _),
  rw [ring_hom.comp_apply, ideal.quotient.eq_zero_iff_mem] at hx,
  refine classical.by_contradiction (λ hx0, absurd (I.eq_top_iff_one.2 _) hI),
  have := I.mul_mem_left (mv_polynomial.C x⁻¹) hx,
  rwa [← mv_polynomial.C.map_mul, inv_mul_cancel hx0, mv_polynomial.C_1] at this,
end

end mv_polynomial

namespace mv_polynomial

universe u
variables {σ : Type u} {K : Type u} [field K]

open_locale classical

lemma dim_mv_polynomial : module.rank K (mv_polynomial σ K) = cardinal.mk (σ →₀ ℕ) :=
by rw [← cardinal.lift_inj, ← (is_basis_monomials σ K).mk_eq_dim]

end mv_polynomial


/-
Copyright (c) 2022 Rémy Degenne. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Rémy Degenne

! This file was ported from Lean 3 source module topology.instances.discrete
! leanprover-community/mathlib commit bcfa726826abd57587355b4b5b7e78ad6527b7e4
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Order.SuccPred.Basic
import Mathbin.Topology.Order.Basic
import Mathbin.Topology.MetricSpace.MetrizableUniformity

/-!
# Instances related to the discrete topology

We prove that the discrete topology is
* first-countable,
* second-countable for an encodable type,
* equal to the order topology in linear orders which are also `pred_order` and `succ_order`,
* metrizable.

When importing this file and `data.nat.succ_pred`, the instances `second_countable_topology ℕ`
and `order_topology ℕ` become available.

-/

open Order Set TopologicalSpace Filter

variable {α : Type _} [TopologicalSpace α]

instance (priority := 100) DiscreteTopology.firstCountableTopology [DiscreteTopology α] :
    FirstCountableTopology α
    where nhds_generated_countable := by
    rw [nhds_discrete]
    exact is_countably_generated_pure
#align discrete_topology.first_countable_topology DiscreteTopology.firstCountableTopology

instance (priority := 100) DiscreteTopology.secondCountableTopology_of_encodable
    [hd : DiscreteTopology α] [Encodable α] : SecondCountableTopology α :=
  haveI : ∀ i : α, second_countable_topology ↥({i} : Set α) := fun i =>
    {
      is_open_generated_countable :=
        ⟨{univ}, countable_singleton _, by simp only [eq_iff_true_of_subsingleton]⟩ }
  second_countable_topology_of_countable_cover (singletons_open_iff_discrete.mpr hd)
    (Union_of_singleton α)
#align discrete_topology.second_countable_topology_of_encodable DiscreteTopology.secondCountableTopology_of_encodable

theorem bot_topologicalSpace_eq_generateFrom_of_pred_succOrder {α} [PartialOrder α] [PredOrder α]
    [SuccOrder α] [NoMinOrder α] [NoMaxOrder α] :
    (⊥ : TopologicalSpace α) = generateFrom { s | ∃ a, s = Ioi a ∨ s = Iio a } :=
  by
  refine' (eq_bot_of_singletons_open fun a => _).symm
  have h_singleton_eq_inter : {a} = Iio (succ a) ∩ Ioi (pred a) :=
    by
    suffices h_singleton_eq_inter' : {a} = Iic a ∩ Ici a
    · rw [h_singleton_eq_inter', ← Ioi_pred, ← Iio_succ]
    rw [inter_comm, Ici_inter_Iic, Icc_self a]
  rw [h_singleton_eq_inter]
  apply IsOpen.inter
  · exact is_open_generate_from_of_mem ⟨succ a, Or.inr rfl⟩
  · exact is_open_generate_from_of_mem ⟨pred a, Or.inl rfl⟩
#align bot_topological_space_eq_generate_from_of_pred_succ_order bot_topologicalSpace_eq_generateFrom_of_pred_succOrder

theorem discreteTopology_iff_orderTopology_of_pred_succ' [PartialOrder α] [PredOrder α]
    [SuccOrder α] [NoMinOrder α] [NoMaxOrder α] : DiscreteTopology α ↔ OrderTopology α :=
  by
  refine' ⟨fun h => ⟨_⟩, fun h => ⟨_⟩⟩
  · rw [h.eq_bot]
    exact bot_topologicalSpace_eq_generateFrom_of_pred_succOrder
  · rw [h.topology_eq_generate_intervals]
    exact bot_topological_space_eq_generate_from_of_pred_succ_order.symm
#align discrete_topology_iff_order_topology_of_pred_succ' discreteTopology_iff_orderTopology_of_pred_succ'

instance (priority := 100) DiscreteTopology.orderTopology_of_pred_succ' [h : DiscreteTopology α]
    [PartialOrder α] [PredOrder α] [SuccOrder α] [NoMinOrder α] [NoMaxOrder α] : OrderTopology α :=
  discreteTopology_iff_orderTopology_of_pred_succ'.1 h
#align discrete_topology.order_topology_of_pred_succ' DiscreteTopology.orderTopology_of_pred_succ'

theorem LinearOrder.bot_topologicalSpace_eq_generateFrom {α} [LinearOrder α] [PredOrder α]
    [SuccOrder α] : (⊥ : TopologicalSpace α) = generateFrom { s | ∃ a, s = Ioi a ∨ s = Iio a } :=
  by
  refine' (eq_bot_of_singletons_open fun a => _).symm
  have h_singleton_eq_inter : {a} = Iic a ∩ Ici a := by rw [inter_comm, Ici_inter_Iic, Icc_self a]
  by_cases ha_top : IsTop a
  · rw [ha_top.Iic_eq, inter_comm, inter_univ] at h_singleton_eq_inter
    by_cases ha_bot : IsBot a
    · rw [ha_bot.Ici_eq] at h_singleton_eq_inter
      rw [h_singleton_eq_inter]
      apply isOpen_univ
    · rw [isBot_iff_isMin] at ha_bot
      rw [← Ioi_pred_of_not_is_min ha_bot] at h_singleton_eq_inter
      rw [h_singleton_eq_inter]
      exact is_open_generate_from_of_mem ⟨pred a, Or.inl rfl⟩
  · rw [isTop_iff_isMax] at ha_top
    rw [← Iio_succ_of_not_is_max ha_top] at h_singleton_eq_inter
    by_cases ha_bot : IsBot a
    · rw [ha_bot.Ici_eq, inter_univ] at h_singleton_eq_inter
      rw [h_singleton_eq_inter]
      exact is_open_generate_from_of_mem ⟨succ a, Or.inr rfl⟩
    · rw [isBot_iff_isMin] at ha_bot
      rw [← Ioi_pred_of_not_is_min ha_bot] at h_singleton_eq_inter
      rw [h_singleton_eq_inter]
      apply IsOpen.inter
      · exact is_open_generate_from_of_mem ⟨succ a, Or.inr rfl⟩
      · exact is_open_generate_from_of_mem ⟨pred a, Or.inl rfl⟩
#align linear_order.bot_topological_space_eq_generate_from LinearOrder.bot_topologicalSpace_eq_generateFrom

theorem discreteTopology_iff_orderTopology_of_pred_succ [LinearOrder α] [PredOrder α]
    [SuccOrder α] : DiscreteTopology α ↔ OrderTopology α :=
  by
  refine' ⟨fun h => ⟨_⟩, fun h => ⟨_⟩⟩
  · rw [h.eq_bot]
    exact LinearOrder.bot_topologicalSpace_eq_generateFrom
  · rw [h.topology_eq_generate_intervals]
    exact linear_order.bot_topological_space_eq_generate_from.symm
#align discrete_topology_iff_order_topology_of_pred_succ discreteTopology_iff_orderTopology_of_pred_succ

instance (priority := 100) DiscreteTopology.orderTopology_of_pred_succ [h : DiscreteTopology α]
    [LinearOrder α] [PredOrder α] [SuccOrder α] : OrderTopology α :=
  discreteTopology_iff_orderTopology_of_pred_succ.mp h
#align discrete_topology.order_topology_of_pred_succ DiscreteTopology.orderTopology_of_pred_succ

instance (priority := 100) DiscreteTopology.metrizableSpace [DiscreteTopology α] :
    MetrizableSpace α := by
  obtain rfl := DiscreteTopology.eq_bot α
  exact @UniformSpace.metrizableSpace α ⊥ (is_countably_generated_principal _) _
#align discrete_topology.metrizable_space DiscreteTopology.metrizableSpace


/-
Copyright (c) 2020 Yury G. Kudryashov. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Yury G. Kudryashov

! This file was ported from Lean 3 source module dynamics.periodic_pts
! leanprover-community/mathlib commit fac369018417f980cec5fcdafc766a69f88d8cfe
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Algebra.Hom.Iterate
import Mathbin.Data.List.Cycle
import Mathbin.Data.Pnat.Basic
import Mathbin.Data.Nat.Prime
import Mathbin.Dynamics.FixedPoints.Basic
import Mathbin.GroupTheory.GroupAction.Group

/-!
# Periodic points

> THIS FILE IS SYNCHRONIZED WITH MATHLIB4.
> Any changes to this file require a corresponding PR to mathlib4.

A point `x : α` is a periodic point of `f : α → α` of period `n` if `f^[n] x = x`.

## Main definitions

* `is_periodic_pt f n x` : `x` is a periodic point of `f` of period `n`, i.e. `f^[n] x = x`.
  We do not require `n > 0` in the definition.
* `pts_of_period f n` : the set `{x | is_periodic_pt f n x}`. Note that `n` is not required to
  be the minimal period of `x`.
* `periodic_pts f` : the set of all periodic points of `f`.
* `minimal_period f x` : the minimal period of a point `x` under an endomorphism `f` or zero
  if `x` is not a periodic point of `f`.
* `orbit f x`: the cycle `[x, f x, f (f x), ...]` for a periodic point.

## Main statements

We provide “dot syntax”-style operations on terms of the form `h : is_periodic_pt f n x` including
arithmetic operations on `n` and `h.map (hg : semiconj_by g f f')`. We also prove that `f`
is bijective on each set `pts_of_period f n` and on `periodic_pts f`. Finally, we prove that `x`
is a periodic point of `f` of period `n` if and only if `minimal_period f x | n`.

## References

* https://en.wikipedia.org/wiki/Periodic_point

-/

open Set

namespace Function

variable {α : Type _} {β : Type _} {f fa : α → α} {fb : β → β} {x y : α} {m n : ℕ}

#print Function.IsPeriodicPt /-
/-- A point `x` is a periodic point of `f : α → α` of period `n` if `f^[n] x = x`.
Note that we do not require `0 < n` in this definition. Many theorems about periodic points
need this assumption. -/
def IsPeriodicPt (f : α → α) (n : ℕ) (x : α) :=
  IsFixedPt (f^[n]) x
#align function.is_periodic_pt Function.IsPeriodicPt
-/

#print Function.IsFixedPt.isPeriodicPt /-
/-- A fixed point of `f` is a periodic point of `f` of any prescribed period. -/
theorem IsFixedPt.isPeriodicPt (hf : IsFixedPt f x) (n : ℕ) : IsPeriodicPt f n x :=
  hf.iterate n
#align function.is_fixed_pt.is_periodic_pt Function.IsFixedPt.isPeriodicPt
-/

#print Function.is_periodic_id /-
/-- For the identity map, all points are periodic. -/
theorem is_periodic_id (n : ℕ) (x : α) : IsPeriodicPt id n x :=
  (isFixedPt_id x).IsPeriodicPt n
#align function.is_periodic_id Function.is_periodic_id
-/

#print Function.isPeriodicPt_zero /-
/-- Any point is a periodic point of period `0`. -/
theorem isPeriodicPt_zero (f : α → α) (x : α) : IsPeriodicPt f 0 x :=
  isFixedPt_id x
#align function.is_periodic_pt_zero Function.isPeriodicPt_zero
-/

namespace IsPeriodicPt

instance [DecidableEq α] {f : α → α} {n : ℕ} {x : α} : Decidable (IsPeriodicPt f n x) :=
  IsFixedPt.decidable

#print Function.IsPeriodicPt.isFixedPt /-
protected theorem isFixedPt (hf : IsPeriodicPt f n x) : IsFixedPt (f^[n]) x :=
  hf
#align function.is_periodic_pt.is_fixed_pt Function.IsPeriodicPt.isFixedPt
-/

/- warning: function.is_periodic_pt.map -> Function.IsPeriodicPt.map is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {fa : α -> α} {fb : β -> β} {x : α} {n : Nat}, (Function.IsPeriodicPt.{u1} α fa n x) -> (forall {g : α -> β}, (Function.Semiconj.{u1, u2} α β g fa fb) -> (Function.IsPeriodicPt.{u2} β fb n (g x)))
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} {fa : α -> α} {fb : β -> β} {x : α} {n : Nat}, (Function.IsPeriodicPt.{u2} α fa n x) -> (forall {g : α -> β}, (Function.Semiconj.{u2, u1} α β g fa fb) -> (Function.IsPeriodicPt.{u1} β fb n (g x)))
Case conversion may be inaccurate. Consider using '#align function.is_periodic_pt.map Function.IsPeriodicPt.mapₓ'. -/
protected theorem map (hx : IsPeriodicPt fa n x) {g : α → β} (hg : Semiconj g fa fb) :
    IsPeriodicPt fb n (g x) :=
  hx.map (hg.iterate_right n)
#align function.is_periodic_pt.map Function.IsPeriodicPt.map

#print Function.IsPeriodicPt.apply_iterate /-
theorem apply_iterate (hx : IsPeriodicPt f n x) (m : ℕ) : IsPeriodicPt f n ((f^[m]) x) :=
  hx.map <| Commute.iterate_self f m
#align function.is_periodic_pt.apply_iterate Function.IsPeriodicPt.apply_iterate
-/

#print Function.IsPeriodicPt.apply /-
protected theorem apply (hx : IsPeriodicPt f n x) : IsPeriodicPt f n (f x) :=
  hx.apply_iterate 1
#align function.is_periodic_pt.apply Function.IsPeriodicPt.apply
-/

#print Function.IsPeriodicPt.add /-
protected theorem add (hn : IsPeriodicPt f n x) (hm : IsPeriodicPt f m x) :
    IsPeriodicPt f (n + m) x := by
  rw [is_periodic_pt, iterate_add]
  exact hn.comp hm
#align function.is_periodic_pt.add Function.IsPeriodicPt.add
-/

#print Function.IsPeriodicPt.left_of_add /-
theorem left_of_add (hn : IsPeriodicPt f (n + m) x) (hm : IsPeriodicPt f m x) :
    IsPeriodicPt f n x := by
  rw [is_periodic_pt, iterate_add] at hn
  exact hn.left_of_comp hm
#align function.is_periodic_pt.left_of_add Function.IsPeriodicPt.left_of_add
-/

#print Function.IsPeriodicPt.right_of_add /-
theorem right_of_add (hn : IsPeriodicPt f (n + m) x) (hm : IsPeriodicPt f n x) :
    IsPeriodicPt f m x := by
  rw [add_comm] at hn
  exact hn.left_of_add hm
#align function.is_periodic_pt.right_of_add Function.IsPeriodicPt.right_of_add
-/

#print Function.IsPeriodicPt.sub /-
protected theorem sub (hm : IsPeriodicPt f m x) (hn : IsPeriodicPt f n x) :
    IsPeriodicPt f (m - n) x := by
  cases' le_total n m with h h
  · refine' left_of_add _ hn
    rwa [tsub_add_cancel_of_le h]
  · rw [tsub_eq_zero_iff_le.mpr h]
    apply is_periodic_pt_zero
#align function.is_periodic_pt.sub Function.IsPeriodicPt.sub
-/

#print Function.IsPeriodicPt.mul_const /-
protected theorem mul_const (hm : IsPeriodicPt f m x) (n : ℕ) : IsPeriodicPt f (m * n) x := by
  simp only [is_periodic_pt, iterate_mul, hm.is_fixed_pt.iterate n]
#align function.is_periodic_pt.mul_const Function.IsPeriodicPt.mul_const
-/

#print Function.IsPeriodicPt.const_mul /-
protected theorem const_mul (hm : IsPeriodicPt f m x) (n : ℕ) : IsPeriodicPt f (n * m) x := by
  simp only [mul_comm n, hm.mul_const n]
#align function.is_periodic_pt.const_mul Function.IsPeriodicPt.const_mul
-/

#print Function.IsPeriodicPt.trans_dvd /-
theorem trans_dvd (hm : IsPeriodicPt f m x) {n : ℕ} (hn : m ∣ n) : IsPeriodicPt f n x :=
  let ⟨k, hk⟩ := hn
  hk.symm ▸ hm.mul_const k
#align function.is_periodic_pt.trans_dvd Function.IsPeriodicPt.trans_dvd
-/

#print Function.IsPeriodicPt.iterate /-
protected theorem iterate (hf : IsPeriodicPt f n x) (m : ℕ) : IsPeriodicPt (f^[m]) n x :=
  by
  rw [is_periodic_pt, ← iterate_mul, mul_comm, iterate_mul]
  exact hf.is_fixed_pt.iterate m
#align function.is_periodic_pt.iterate Function.IsPeriodicPt.iterate
-/

#print Function.IsPeriodicPt.comp /-
theorem comp {g : α → α} (hco : Commute f g) (hf : IsPeriodicPt f n x) (hg : IsPeriodicPt g n x) :
    IsPeriodicPt (f ∘ g) n x :=
  by
  rw [is_periodic_pt, hco.comp_iterate]
  exact hf.comp hg
#align function.is_periodic_pt.comp Function.IsPeriodicPt.comp
-/

#print Function.IsPeriodicPt.comp_lcm /-
theorem comp_lcm {g : α → α} (hco : Commute f g) (hf : IsPeriodicPt f m x)
    (hg : IsPeriodicPt g n x) : IsPeriodicPt (f ∘ g) (Nat.lcm m n) x :=
  (hf.trans_dvd <| Nat.dvd_lcm_left _ _).comp hco (hg.trans_dvd <| Nat.dvd_lcm_right _ _)
#align function.is_periodic_pt.comp_lcm Function.IsPeriodicPt.comp_lcm
-/

#print Function.IsPeriodicPt.left_of_comp /-
theorem left_of_comp {g : α → α} (hco : Commute f g) (hfg : IsPeriodicPt (f ∘ g) n x)
    (hg : IsPeriodicPt g n x) : IsPeriodicPt f n x :=
  by
  rw [is_periodic_pt, hco.comp_iterate] at hfg
  exact hfg.left_of_comp hg
#align function.is_periodic_pt.left_of_comp Function.IsPeriodicPt.left_of_comp
-/

#print Function.IsPeriodicPt.iterate_mod_apply /-
theorem iterate_mod_apply (h : IsPeriodicPt f n x) (m : ℕ) : (f^[m % n]) x = (f^[m]) x := by
  conv_rhs => rw [← Nat.mod_add_div m n, iterate_add_apply, (h.mul_const _).Eq]
#align function.is_periodic_pt.iterate_mod_apply Function.IsPeriodicPt.iterate_mod_apply
-/

#print Function.IsPeriodicPt.mod /-
protected theorem mod (hm : IsPeriodicPt f m x) (hn : IsPeriodicPt f n x) :
    IsPeriodicPt f (m % n) x :=
  (hn.iterate_mod_apply m).trans hm
#align function.is_periodic_pt.mod Function.IsPeriodicPt.mod
-/

#print Function.IsPeriodicPt.gcd /-
protected theorem gcd (hm : IsPeriodicPt f m x) (hn : IsPeriodicPt f n x) :
    IsPeriodicPt f (m.gcd n) x := by
  revert hm hn
  refine' Nat.gcd.induction m n (fun n h0 hn => _) fun m n hm ih hm hn => _
  · rwa [Nat.gcd_zero_left]
  · rw [Nat.gcd_rec]
    exact ih (hn.mod hm) hm
#align function.is_periodic_pt.gcd Function.IsPeriodicPt.gcd
-/

#print Function.IsPeriodicPt.eq_of_apply_eq_same /-
/-- If `f` sends two periodic points `x` and `y` of the same positive period to the same point,
then `x = y`. For a similar statement about points of different periods see `eq_of_apply_eq`. -/
theorem eq_of_apply_eq_same (hx : IsPeriodicPt f n x) (hy : IsPeriodicPt f n y) (hn : 0 < n)
    (h : f x = f y) : x = y := by
  rw [← hx.eq, ← hy.eq, ← iterate_pred_comp_of_pos f hn, comp_app, h]
#align function.is_periodic_pt.eq_of_apply_eq_same Function.IsPeriodicPt.eq_of_apply_eq_same
-/

#print Function.IsPeriodicPt.eq_of_apply_eq /-
/-- If `f` sends two periodic points `x` and `y` of positive periods to the same point,
then `x = y`. -/
theorem eq_of_apply_eq (hx : IsPeriodicPt f m x) (hy : IsPeriodicPt f n y) (hm : 0 < m) (hn : 0 < n)
    (h : f x = f y) : x = y :=
  (hx.mul_const n).eq_of_apply_eq_same (hy.const_mul m) (mul_pos hm hn) h
#align function.is_periodic_pt.eq_of_apply_eq Function.IsPeriodicPt.eq_of_apply_eq
-/

end IsPeriodicPt

#print Function.ptsOfPeriod /-
/-- The set of periodic points of a given (possibly non-minimal) period. -/
def ptsOfPeriod (f : α → α) (n : ℕ) : Set α :=
  { x : α | IsPeriodicPt f n x }
#align function.pts_of_period Function.ptsOfPeriod
-/

#print Function.mem_ptsOfPeriod /-
@[simp]
theorem mem_ptsOfPeriod : x ∈ ptsOfPeriod f n ↔ IsPeriodicPt f n x :=
  Iff.rfl
#align function.mem_pts_of_period Function.mem_ptsOfPeriod
-/

/- warning: function.semiconj.maps_to_pts_of_period -> Function.Semiconj.mapsTo_ptsOfPeriod is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {fa : α -> α} {fb : β -> β} {g : α -> β}, (Function.Semiconj.{u1, u2} α β g fa fb) -> (forall (n : Nat), Set.MapsTo.{u1, u2} α β g (Function.ptsOfPeriod.{u1} α fa n) (Function.ptsOfPeriod.{u2} β fb n))
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} {fa : α -> α} {fb : β -> β} {g : α -> β}, (Function.Semiconj.{u2, u1} α β g fa fb) -> (forall (n : Nat), Set.MapsTo.{u2, u1} α β g (Function.ptsOfPeriod.{u2} α fa n) (Function.ptsOfPeriod.{u1} β fb n))
Case conversion may be inaccurate. Consider using '#align function.semiconj.maps_to_pts_of_period Function.Semiconj.mapsTo_ptsOfPeriodₓ'. -/
theorem Semiconj.mapsTo_ptsOfPeriod {g : α → β} (h : Semiconj g fa fb) (n : ℕ) :
    MapsTo g (ptsOfPeriod fa n) (ptsOfPeriod fb n) :=
  (h.iterate_right n).mapsTo_fixedPoints
#align function.semiconj.maps_to_pts_of_period Function.Semiconj.mapsTo_ptsOfPeriod

#print Function.bijOn_ptsOfPeriod /-
theorem bijOn_ptsOfPeriod (f : α → α) {n : ℕ} (hn : 0 < n) :
    BijOn f (ptsOfPeriod f n) (ptsOfPeriod f n) :=
  ⟨(Commute.refl f).mapsTo_ptsOfPeriod n, fun x hx y hy hxy => hx.eq_of_apply_eq_same hy hn hxy,
    fun x hx =>
    ⟨(f^[n.pred]) x, hx.apply_iterate _, by
      rw [← comp_app f, comp_iterate_pred_of_pos f hn, hx.eq]⟩⟩
#align function.bij_on_pts_of_period Function.bijOn_ptsOfPeriod
-/

#print Function.directed_ptsOfPeriod_pNat /-
theorem directed_ptsOfPeriod_pNat (f : α → α) : Directed (· ⊆ ·) fun n : ℕ+ => ptsOfPeriod f n :=
  fun m n => ⟨m * n, fun x hx => hx.mul_const n, fun x hx => hx.const_mul m⟩
#align function.directed_pts_of_period_pnat Function.directed_ptsOfPeriod_pNat
-/

#print Function.periodicPts /-
/-- The set of periodic points of a map `f : α → α`. -/
def periodicPts (f : α → α) : Set α :=
  { x : α | ∃ n > 0, IsPeriodicPt f n x }
#align function.periodic_pts Function.periodicPts
-/

#print Function.mk_mem_periodicPts /-
theorem mk_mem_periodicPts (hn : 0 < n) (hx : IsPeriodicPt f n x) : x ∈ periodicPts f :=
  ⟨n, hn, hx⟩
#align function.mk_mem_periodic_pts Function.mk_mem_periodicPts
-/

/- warning: function.mem_periodic_pts -> Function.mem_periodicPts is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {f : α -> α} {x : α}, Iff (Membership.Mem.{u1, u1} α (Set.{u1} α) (Set.hasMem.{u1} α) x (Function.periodicPts.{u1} α f)) (Exists.{1} Nat (fun (n : Nat) => Exists.{0} (GT.gt.{0} Nat Nat.hasLt n (OfNat.ofNat.{0} Nat 0 (OfNat.mk.{0} Nat 0 (Zero.zero.{0} Nat Nat.hasZero)))) (fun (H : GT.gt.{0} Nat Nat.hasLt n (OfNat.ofNat.{0} Nat 0 (OfNat.mk.{0} Nat 0 (Zero.zero.{0} Nat Nat.hasZero)))) => Function.IsPeriodicPt.{u1} α f n x)))
but is expected to have type
  forall {α : Type.{u1}} {f : α -> α} {x : α}, Iff (Membership.mem.{u1, u1} α (Set.{u1} α) (Set.instMembershipSet.{u1} α) x (Function.periodicPts.{u1} α f)) (Exists.{1} Nat (fun (n : Nat) => And (GT.gt.{0} Nat instLTNat n (OfNat.ofNat.{0} Nat 0 (instOfNatNat 0))) (Function.IsPeriodicPt.{u1} α f n x)))
Case conversion may be inaccurate. Consider using '#align function.mem_periodic_pts Function.mem_periodicPtsₓ'. -/
theorem mem_periodicPts : x ∈ periodicPts f ↔ ∃ n > 0, IsPeriodicPt f n x :=
  Iff.rfl
#align function.mem_periodic_pts Function.mem_periodicPts

#print Function.isPeriodicPt_of_mem_periodicPts_of_isPeriodicPt_iterate /-
theorem isPeriodicPt_of_mem_periodicPts_of_isPeriodicPt_iterate (hx : x ∈ periodicPts f)
    (hm : IsPeriodicPt f m ((f^[n]) x)) : IsPeriodicPt f m x :=
  by
  rcases hx with ⟨r, hr, hr'⟩
  convert(hm.apply_iterate ((n / r + 1) * r - n)).Eq
  suffices n ≤ (n / r + 1) * r by
    rw [← iterate_add_apply, Nat.sub_add_cancel this, iterate_mul, (hr'.iterate _).Eq]
  rw [add_mul, one_mul]
  exact (Nat.lt_div_mul_add hr).le
#align function.is_periodic_pt_of_mem_periodic_pts_of_is_periodic_pt_iterate Function.isPeriodicPt_of_mem_periodicPts_of_isPeriodicPt_iterate
-/

variable (f)

#print Function.bUnion_ptsOfPeriod /-
theorem bUnion_ptsOfPeriod : (⋃ n > 0, ptsOfPeriod f n) = periodicPts f :=
  Set.ext fun x => by simp [mem_periodic_pts]
#align function.bUnion_pts_of_period Function.bUnion_ptsOfPeriod
-/

#print Function.unionᵢ_pNat_ptsOfPeriod /-
theorem unionᵢ_pNat_ptsOfPeriod : (⋃ n : ℕ+, ptsOfPeriod f n) = periodicPts f :=
  supᵢ_subtype.trans <| bUnion_ptsOfPeriod f
#align function.Union_pnat_pts_of_period Function.unionᵢ_pNat_ptsOfPeriod
-/

#print Function.bijOn_periodicPts /-
theorem bijOn_periodicPts : BijOn f (periodicPts f) (periodicPts f) :=
  unionᵢ_pNat_ptsOfPeriod f ▸
    bijOn_unionᵢ_of_directed (directed_ptsOfPeriod_pNat f) fun i => bijOn_ptsOfPeriod f i.Pos
#align function.bij_on_periodic_pts Function.bijOn_periodicPts
-/

variable {f}

/- warning: function.semiconj.maps_to_periodic_pts -> Function.Semiconj.mapsTo_periodicPts is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {fa : α -> α} {fb : β -> β} {g : α -> β}, (Function.Semiconj.{u1, u2} α β g fa fb) -> (Set.MapsTo.{u1, u2} α β g (Function.periodicPts.{u1} α fa) (Function.periodicPts.{u2} β fb))
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} {fa : α -> α} {fb : β -> β} {g : α -> β}, (Function.Semiconj.{u2, u1} α β g fa fb) -> (Set.MapsTo.{u2, u1} α β g (Function.periodicPts.{u2} α fa) (Function.periodicPts.{u1} β fb))
Case conversion may be inaccurate. Consider using '#align function.semiconj.maps_to_periodic_pts Function.Semiconj.mapsTo_periodicPtsₓ'. -/
theorem Semiconj.mapsTo_periodicPts {g : α → β} (h : Semiconj g fa fb) :
    MapsTo g (periodicPts fa) (periodicPts fb) := fun x ⟨n, hn, hx⟩ => ⟨n, hn, hx.map h⟩
#align function.semiconj.maps_to_periodic_pts Function.Semiconj.mapsTo_periodicPts

open Classical

noncomputable section

#print Function.minimalPeriod /-
/-- Minimal period of a point `x` under an endomorphism `f`. If `x` is not a periodic point of `f`,
then `minimal_period f x = 0`. -/
def minimalPeriod (f : α → α) (x : α) :=
  if h : x ∈ periodicPts f then Nat.find h else 0
#align function.minimal_period Function.minimalPeriod
-/

#print Function.isPeriodicPt_minimalPeriod /-
theorem isPeriodicPt_minimalPeriod (f : α → α) (x : α) : IsPeriodicPt f (minimalPeriod f x) x :=
  by
  delta minimal_period
  split_ifs with hx
  · exact (Nat.find_spec hx).snd
  · exact is_periodic_pt_zero f x
#align function.is_periodic_pt_minimal_period Function.isPeriodicPt_minimalPeriod
-/

#print Function.iterate_minimalPeriod /-
@[simp]
theorem iterate_minimalPeriod : (f^[minimalPeriod f x]) x = x :=
  isPeriodicPt_minimalPeriod f x
#align function.iterate_minimal_period Function.iterate_minimalPeriod
-/

#print Function.iterate_add_minimalPeriod_eq /-
@[simp]
theorem iterate_add_minimalPeriod_eq : (f^[n + minimalPeriod f x]) x = (f^[n]) x :=
  by
  rw [iterate_add_apply]
  congr
  exact is_periodic_pt_minimal_period f x
#align function.iterate_add_minimal_period_eq Function.iterate_add_minimalPeriod_eq
-/

#print Function.iterate_mod_minimalPeriod_eq /-
@[simp]
theorem iterate_mod_minimalPeriod_eq : (f^[n % minimalPeriod f x]) x = (f^[n]) x :=
  (isPeriodicPt_minimalPeriod f x).iterate_mod_apply n
#align function.iterate_mod_minimal_period_eq Function.iterate_mod_minimalPeriod_eq
-/

#print Function.minimalPeriod_pos_of_mem_periodicPts /-
theorem minimalPeriod_pos_of_mem_periodicPts (hx : x ∈ periodicPts f) : 0 < minimalPeriod f x := by
  simp only [minimal_period, dif_pos hx, (Nat.find_spec hx).fst.lt]
#align function.minimal_period_pos_of_mem_periodic_pts Function.minimalPeriod_pos_of_mem_periodicPts
-/

#print Function.minimalPeriod_eq_zero_of_nmem_periodicPts /-
theorem minimalPeriod_eq_zero_of_nmem_periodicPts (hx : x ∉ periodicPts f) :
    minimalPeriod f x = 0 := by simp only [minimal_period, dif_neg hx]
#align function.minimal_period_eq_zero_of_nmem_periodic_pts Function.minimalPeriod_eq_zero_of_nmem_periodicPts
-/

#print Function.IsPeriodicPt.minimalPeriod_pos /-
theorem IsPeriodicPt.minimalPeriod_pos (hn : 0 < n) (hx : IsPeriodicPt f n x) :
    0 < minimalPeriod f x :=
  minimalPeriod_pos_of_mem_periodicPts <| mk_mem_periodicPts hn hx
#align function.is_periodic_pt.minimal_period_pos Function.IsPeriodicPt.minimalPeriod_pos
-/

#print Function.minimalPeriod_pos_iff_mem_periodicPts /-
theorem minimalPeriod_pos_iff_mem_periodicPts : 0 < minimalPeriod f x ↔ x ∈ periodicPts f :=
  ⟨not_imp_not.1 fun h => by simp only [minimal_period, dif_neg h, lt_irrefl 0, not_false_iff],
    minimalPeriod_pos_of_mem_periodicPts⟩
#align function.minimal_period_pos_iff_mem_periodic_pts Function.minimalPeriod_pos_iff_mem_periodicPts
-/

#print Function.minimalPeriod_eq_zero_iff_nmem_periodicPts /-
theorem minimalPeriod_eq_zero_iff_nmem_periodicPts : minimalPeriod f x = 0 ↔ x ∉ periodicPts f := by
  rw [← minimal_period_pos_iff_mem_periodic_pts, not_lt, nonpos_iff_eq_zero]
#align function.minimal_period_eq_zero_iff_nmem_periodic_pts Function.minimalPeriod_eq_zero_iff_nmem_periodicPts
-/

#print Function.IsPeriodicPt.minimalPeriod_le /-
theorem IsPeriodicPt.minimalPeriod_le (hn : 0 < n) (hx : IsPeriodicPt f n x) :
    minimalPeriod f x ≤ n :=
  by
  rw [minimal_period, dif_pos (mk_mem_periodic_pts hn hx)]
  exact Nat.find_min' (mk_mem_periodic_pts hn hx) ⟨hn, hx⟩
#align function.is_periodic_pt.minimal_period_le Function.IsPeriodicPt.minimalPeriod_le
-/

#print Function.minimalPeriod_apply_iterate /-
theorem minimalPeriod_apply_iterate (hx : x ∈ periodicPts f) (n : ℕ) :
    minimalPeriod f ((f^[n]) x) = minimalPeriod f x :=
  by
  apply
    (is_periodic_pt.minimal_period_le (minimal_period_pos_of_mem_periodic_pts hx) _).antisymm
      ((is_periodic_pt_of_mem_periodic_pts_of_is_periodic_pt_iterate hx
            (is_periodic_pt_minimal_period f _)).minimalPeriod_le
        (minimal_period_pos_of_mem_periodic_pts _))
  · exact (is_periodic_pt_minimal_period f x).apply_iterate n
  · rcases hx with ⟨m, hm, hx⟩
    exact ⟨m, hm, hx.apply_iterate n⟩
#align function.minimal_period_apply_iterate Function.minimalPeriod_apply_iterate
-/

#print Function.minimalPeriod_apply /-
theorem minimalPeriod_apply (hx : x ∈ periodicPts f) : minimalPeriod f (f x) = minimalPeriod f x :=
  minimalPeriod_apply_iterate hx 1
#align function.minimal_period_apply Function.minimalPeriod_apply
-/

#print Function.le_of_lt_minimalPeriod_of_iterate_eq /-
theorem le_of_lt_minimalPeriod_of_iterate_eq {m n : ℕ} (hm : m < minimalPeriod f x)
    (hmn : (f^[m]) x = (f^[n]) x) : m ≤ n :=
  by
  by_contra' hmn'
  rw [← Nat.add_sub_of_le hmn'.le, add_comm, iterate_add_apply] at hmn
  exact
    ((is_periodic_pt.minimal_period_le (tsub_pos_of_lt hmn')
              (is_periodic_pt_of_mem_periodic_pts_of_is_periodic_pt_iterate
                (minimal_period_pos_iff_mem_periodic_pts.1 ((zero_le m).trans_lt hm)) hmn)).trans
          (Nat.sub_le m n)).not_lt
      hm
#align function.le_of_lt_minimal_period_of_iterate_eq Function.le_of_lt_minimalPeriod_of_iterate_eq
-/

#print Function.eq_of_lt_minimalPeriod_of_iterate_eq /-
theorem eq_of_lt_minimalPeriod_of_iterate_eq {m n : ℕ} (hm : m < minimalPeriod f x)
    (hn : n < minimalPeriod f x) (hmn : (f^[m]) x = (f^[n]) x) : m = n :=
  (le_of_lt_minimalPeriod_of_iterate_eq hm hmn).antisymm
    (le_of_lt_minimalPeriod_of_iterate_eq hn hmn.symm)
#align function.eq_of_lt_minimal_period_of_iterate_eq Function.eq_of_lt_minimalPeriod_of_iterate_eq
-/

#print Function.eq_iff_lt_minimalPeriod_of_iterate_eq /-
theorem eq_iff_lt_minimalPeriod_of_iterate_eq {m n : ℕ} (hm : m < minimalPeriod f x)
    (hn : n < minimalPeriod f x) : (f^[m]) x = (f^[n]) x ↔ m = n :=
  ⟨eq_of_lt_minimalPeriod_of_iterate_eq hm hn, congr_arg _⟩
#align function.eq_iff_lt_minimal_period_of_iterate_eq Function.eq_iff_lt_minimalPeriod_of_iterate_eq
-/

#print Function.minimalPeriod_id /-
theorem minimalPeriod_id : minimalPeriod id x = 1 :=
  ((is_periodic_id _ _).minimalPeriod_le Nat.one_pos).antisymm
    (Nat.succ_le_of_lt ((is_periodic_id _ _).minimalPeriod_pos Nat.one_pos))
#align function.minimal_period_id Function.minimalPeriod_id
-/

#print Function.is_fixed_point_iff_minimalPeriod_eq_one /-
theorem is_fixed_point_iff_minimalPeriod_eq_one : minimalPeriod f x = 1 ↔ IsFixedPt f x :=
  by
  refine' ⟨fun h => _, fun h => _⟩
  · rw [← iterate_one f]
    refine' Function.IsPeriodicPt.isFixedPt _
    rw [← h]
    exact is_periodic_pt_minimal_period f x
  ·
    exact
      ((h.is_periodic_pt 1).minimalPeriod_le Nat.one_pos).antisymm
        (Nat.succ_le_of_lt ((h.is_periodic_pt 1).minimalPeriod_pos Nat.one_pos))
#align function.is_fixed_point_iff_minimal_period_eq_one Function.is_fixed_point_iff_minimalPeriod_eq_one
-/

#print Function.IsPeriodicPt.eq_zero_of_lt_minimalPeriod /-
theorem IsPeriodicPt.eq_zero_of_lt_minimalPeriod (hx : IsPeriodicPt f n x)
    (hn : n < minimalPeriod f x) : n = 0 :=
  Eq.symm <|
    (eq_or_lt_of_le <| n.zero_le).resolve_right fun hn0 => not_lt.2 (hx.minimalPeriod_le hn0) hn
#align function.is_periodic_pt.eq_zero_of_lt_minimal_period Function.IsPeriodicPt.eq_zero_of_lt_minimalPeriod
-/

#print Function.not_isPeriodicPt_of_pos_of_lt_minimalPeriod /-
theorem not_isPeriodicPt_of_pos_of_lt_minimalPeriod :
    ∀ {n : ℕ} (n0 : n ≠ 0) (hn : n < minimalPeriod f x), ¬IsPeriodicPt f n x
  | 0, n0, _ => (n0 rfl).elim
  | n + 1, _, hn => fun hp => Nat.succ_ne_zero _ (hp.eq_zero_of_lt_minimalPeriod hn)
#align function.not_is_periodic_pt_of_pos_of_lt_minimal_period Function.not_isPeriodicPt_of_pos_of_lt_minimalPeriod
-/

#print Function.IsPeriodicPt.minimalPeriod_dvd /-
theorem IsPeriodicPt.minimalPeriod_dvd (hx : IsPeriodicPt f n x) : minimalPeriod f x ∣ n :=
  (eq_or_lt_of_le <| n.zero_le).elim (fun hn0 => hn0 ▸ dvd_zero _) fun hn0 =>
    Nat.dvd_iff_mod_eq_zero.2 <|
      (hx.mod <| isPeriodicPt_minimalPeriod f x).eq_zero_of_lt_minimalPeriod <|
        Nat.mod_lt _ <| hx.minimalPeriod_pos hn0
#align function.is_periodic_pt.minimal_period_dvd Function.IsPeriodicPt.minimalPeriod_dvd
-/

#print Function.isPeriodicPt_iff_minimalPeriod_dvd /-
theorem isPeriodicPt_iff_minimalPeriod_dvd : IsPeriodicPt f n x ↔ minimalPeriod f x ∣ n :=
  ⟨IsPeriodicPt.minimalPeriod_dvd, fun h => (isPeriodicPt_minimalPeriod f x).trans_dvd h⟩
#align function.is_periodic_pt_iff_minimal_period_dvd Function.isPeriodicPt_iff_minimalPeriod_dvd
-/

open Nat

/- warning: function.minimal_period_eq_minimal_period_iff -> Function.minimalPeriod_eq_minimalPeriod_iff is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} {f : α -> α} {x : α} {g : β -> β} {y : β}, Iff (Eq.{1} Nat (Function.minimalPeriod.{u1} α f x) (Function.minimalPeriod.{u2} β g y)) (forall (n : Nat), Iff (Function.IsPeriodicPt.{u1} α f n x) (Function.IsPeriodicPt.{u2} β g n y))
but is expected to have type
  forall {α : Type.{u2}} {β : Type.{u1}} {f : α -> α} {x : α} {g : β -> β} {y : β}, Iff (Eq.{1} Nat (Function.minimalPeriod.{u2} α f x) (Function.minimalPeriod.{u1} β g y)) (forall (n : Nat), Iff (Function.IsPeriodicPt.{u2} α f n x) (Function.IsPeriodicPt.{u1} β g n y))
Case conversion may be inaccurate. Consider using '#align function.minimal_period_eq_minimal_period_iff Function.minimalPeriod_eq_minimalPeriod_iffₓ'. -/
theorem minimalPeriod_eq_minimalPeriod_iff {g : β → β} {y : β} :
    minimalPeriod f x = minimalPeriod g y ↔ ∀ n, IsPeriodicPt f n x ↔ IsPeriodicPt g n y := by
  simp_rw [is_periodic_pt_iff_minimal_period_dvd, dvd_right_iff_eq]
#align function.minimal_period_eq_minimal_period_iff Function.minimalPeriod_eq_minimalPeriod_iff

#print Function.minimalPeriod_eq_prime /-
theorem minimalPeriod_eq_prime {p : ℕ} [hp : Fact p.Prime] (hper : IsPeriodicPt f p x)
    (hfix : ¬IsFixedPt f x) : minimalPeriod f x = p :=
  (hp.out.eq_one_or_self_of_dvd _ hper.minimalPeriod_dvd).resolve_left
    (mt is_fixed_point_iff_minimalPeriod_eq_one.1 hfix)
#align function.minimal_period_eq_prime Function.minimalPeriod_eq_prime
-/

#print Function.minimalPeriod_eq_prime_pow /-
theorem minimalPeriod_eq_prime_pow {p k : ℕ} [hp : Fact p.Prime] (hk : ¬IsPeriodicPt f (p ^ k) x)
    (hk1 : IsPeriodicPt f (p ^ (k + 1)) x) : minimalPeriod f x = p ^ (k + 1) := by
  apply Nat.eq_prime_pow_of_dvd_least_prime_pow hp.out <;>
    rwa [← is_periodic_pt_iff_minimal_period_dvd]
#align function.minimal_period_eq_prime_pow Function.minimalPeriod_eq_prime_pow
-/

#print Function.Commute.minimalPeriod_of_comp_dvd_lcm /-
theorem Commute.minimalPeriod_of_comp_dvd_lcm {g : α → α} (h : Function.Commute f g) :
    minimalPeriod (f ∘ g) x ∣ Nat.lcm (minimalPeriod f x) (minimalPeriod g x) :=
  by
  rw [← is_periodic_pt_iff_minimal_period_dvd]
  exact (is_periodic_pt_minimal_period f x).comp_lcm h (is_periodic_pt_minimal_period g x)
#align function.commute.minimal_period_of_comp_dvd_lcm Function.Commute.minimalPeriod_of_comp_dvd_lcm
-/

#print Function.Commute.minimalPeriod_of_comp_dvd_mul /-
theorem Commute.minimalPeriod_of_comp_dvd_mul {g : α → α} (h : Function.Commute f g) :
    minimalPeriod (f ∘ g) x ∣ minimalPeriod f x * minimalPeriod g x :=
  dvd_trans h.minimalPeriod_of_comp_dvd_lcm (lcm_dvd_mul _ _)
#align function.commute.minimal_period_of_comp_dvd_mul Function.Commute.minimalPeriod_of_comp_dvd_mul
-/

#print Function.Commute.minimalPeriod_of_comp_eq_mul_of_coprime /-
theorem Commute.minimalPeriod_of_comp_eq_mul_of_coprime {g : α → α} (h : Function.Commute f g)
    (hco : coprime (minimalPeriod f x) (minimalPeriod g x)) :
    minimalPeriod (f ∘ g) x = minimalPeriod f x * minimalPeriod g x :=
  by
  apply dvd_antisymm h.minimal_period_of_comp_dvd_mul
  suffices :
    ∀ {f g : α → α},
      Commute f g →
        coprime (minimal_period f x) (minimal_period g x) →
          minimal_period f x ∣ minimal_period (f ∘ g) x
  exact hco.mul_dvd_of_dvd_of_dvd (this h hco) (h.comp_eq.symm ▸ this h.symm hco.symm)
  clear hco h f g
  intro f g h hco
  refine' hco.dvd_of_dvd_mul_left (is_periodic_pt.left_of_comp h _ _).minimalPeriod_dvd
  · exact (is_periodic_pt_minimal_period _ _).const_mul _
  · exact (is_periodic_pt_minimal_period _ _).mul_const _
#align function.commute.minimal_period_of_comp_eq_mul_of_coprime Function.Commute.minimalPeriod_of_comp_eq_mul_of_coprime
-/

private theorem minimal_period_iterate_eq_div_gcd_aux (h : 0 < gcd (minimalPeriod f x) n) :
    minimalPeriod (f^[n]) x = minimalPeriod f x / Nat.gcd (minimalPeriod f x) n :=
  by
  apply Nat.dvd_antisymm
  · apply is_periodic_pt.minimal_period_dvd
    rw [is_periodic_pt, is_fixed_pt, ← iterate_mul, ← Nat.mul_div_assoc _ (gcd_dvd_left _ _),
      mul_comm, Nat.mul_div_assoc _ (gcd_dvd_right _ _), mul_comm, iterate_mul]
    exact (is_periodic_pt_minimal_period f x).iterate _
  · apply coprime.dvd_of_dvd_mul_right (coprime_div_gcd_div_gcd h)
    apply dvd_of_mul_dvd_mul_right h
    rw [Nat.div_mul_cancel (gcd_dvd_left _ _), mul_assoc, Nat.div_mul_cancel (gcd_dvd_right _ _),
      mul_comm]
    apply is_periodic_pt.minimal_period_dvd
    rw [is_periodic_pt, is_fixed_pt, iterate_mul]
    exact is_periodic_pt_minimal_period _ _
#align function.minimal_period_iterate_eq_div_gcd_aux function.minimal_period_iterate_eq_div_gcd_aux

#print Function.minimalPeriod_iterate_eq_div_gcd /-
theorem minimalPeriod_iterate_eq_div_gcd (h : n ≠ 0) :
    minimalPeriod (f^[n]) x = minimalPeriod f x / Nat.gcd (minimalPeriod f x) n :=
  minimalPeriod_iterate_eq_div_gcd_aux <| gcd_pos_of_pos_right _ (Nat.pos_of_ne_zero h)
#align function.minimal_period_iterate_eq_div_gcd Function.minimalPeriod_iterate_eq_div_gcd
-/

#print Function.minimalPeriod_iterate_eq_div_gcd' /-
theorem minimalPeriod_iterate_eq_div_gcd' (h : x ∈ periodicPts f) :
    minimalPeriod (f^[n]) x = minimalPeriod f x / Nat.gcd (minimalPeriod f x) n :=
  minimalPeriod_iterate_eq_div_gcd_aux <|
    gcd_pos_of_pos_left n (minimalPeriod_pos_iff_mem_periodicPts.mpr h)
#align function.minimal_period_iterate_eq_div_gcd' Function.minimalPeriod_iterate_eq_div_gcd'
-/

#print Function.periodicOrbit /-
/-- The orbit of a periodic point `x` of `f` is the cycle `[x, f x, f (f x), ...]`. Its length is
the minimal period of `x`.

If `x` is not a periodic point, then this is the empty (aka nil) cycle. -/
def periodicOrbit (f : α → α) (x : α) : Cycle α :=
  (List.range (minimalPeriod f x)).map fun n => (f^[n]) x
#align function.periodic_orbit Function.periodicOrbit
-/

#print Function.periodicOrbit_def /-
/-- The definition of a periodic orbit, in terms of `list.map`. -/
theorem periodicOrbit_def (f : α → α) (x : α) :
    periodicOrbit f x = (List.range (minimalPeriod f x)).map fun n => (f^[n]) x :=
  rfl
#align function.periodic_orbit_def Function.periodicOrbit_def
-/

#print Function.periodicOrbit_eq_cycle_map /-
/-- The definition of a periodic orbit, in terms of `cycle.map`. -/
theorem periodicOrbit_eq_cycle_map (f : α → α) (x : α) :
    periodicOrbit f x = (List.range (minimalPeriod f x) : Cycle ℕ).map fun n => (f^[n]) x :=
  rfl
#align function.periodic_orbit_eq_cycle_map Function.periodicOrbit_eq_cycle_map
-/

#print Function.periodicOrbit_length /-
@[simp]
theorem periodicOrbit_length : (periodicOrbit f x).length = minimalPeriod f x := by
  rw [periodic_orbit, Cycle.length_coe, List.length_map, List.length_range]
#align function.periodic_orbit_length Function.periodicOrbit_length
-/

#print Function.periodicOrbit_eq_nil_iff_not_periodic_pt /-
@[simp]
theorem periodicOrbit_eq_nil_iff_not_periodic_pt :
    periodicOrbit f x = Cycle.nil ↔ x ∉ periodicPts f :=
  by
  simp [periodic_orbit]
  exact minimal_period_eq_zero_iff_nmem_periodic_pts
#align function.periodic_orbit_eq_nil_iff_not_periodic_pt Function.periodicOrbit_eq_nil_iff_not_periodic_pt
-/

#print Function.periodicOrbit_eq_nil_of_not_periodic_pt /-
theorem periodicOrbit_eq_nil_of_not_periodic_pt (h : x ∉ periodicPts f) :
    periodicOrbit f x = Cycle.nil :=
  periodicOrbit_eq_nil_iff_not_periodic_pt.2 h
#align function.periodic_orbit_eq_nil_of_not_periodic_pt Function.periodicOrbit_eq_nil_of_not_periodic_pt
-/

#print Function.mem_periodicOrbit_iff /-
@[simp]
theorem mem_periodicOrbit_iff (hx : x ∈ periodicPts f) :
    y ∈ periodicOrbit f x ↔ ∃ n, (f^[n]) x = y :=
  by
  simp only [periodic_orbit, Cycle.mem_coe_iff, List.mem_map, List.mem_range]
  use fun ⟨a, ha, ha'⟩ => ⟨a, ha'⟩
  rintro ⟨n, rfl⟩
  use n % minimal_period f x, mod_lt _ (minimal_period_pos_of_mem_periodic_pts hx)
  rw [iterate_mod_minimal_period_eq]
#align function.mem_periodic_orbit_iff Function.mem_periodicOrbit_iff
-/

#print Function.iterate_mem_periodicOrbit /-
@[simp]
theorem iterate_mem_periodicOrbit (hx : x ∈ periodicPts f) (n : ℕ) :
    (f^[n]) x ∈ periodicOrbit f x :=
  (mem_periodicOrbit_iff hx).2 ⟨n, rfl⟩
#align function.iterate_mem_periodic_orbit Function.iterate_mem_periodicOrbit
-/

#print Function.self_mem_periodicOrbit /-
@[simp]
theorem self_mem_periodicOrbit (hx : x ∈ periodicPts f) : x ∈ periodicOrbit f x :=
  iterate_mem_periodicOrbit hx 0
#align function.self_mem_periodic_orbit Function.self_mem_periodicOrbit
-/

#print Function.nodup_periodicOrbit /-
theorem nodup_periodicOrbit : (periodicOrbit f x).Nodup :=
  by
  rw [periodic_orbit, Cycle.nodup_coe_iff, List.nodup_map_iff_inj_on (List.nodup_range _)]
  intro m hm n hn hmn
  rw [List.mem_range] at hm hn
  rwa [eq_iff_lt_minimal_period_of_iterate_eq hm hn] at hmn
#align function.nodup_periodic_orbit Function.nodup_periodicOrbit
-/

#print Function.periodicOrbit_apply_iterate_eq /-
theorem periodicOrbit_apply_iterate_eq (hx : x ∈ periodicPts f) (n : ℕ) :
    periodicOrbit f ((f^[n]) x) = periodicOrbit f x :=
  Eq.symm <|
    Cycle.coe_eq_coe.2 <|
      ⟨n, by
        apply List.ext_nthLe _ fun m _ _ => _
        · simp [minimal_period_apply_iterate hx]
        · rw [List.nthLe_rotate _ n m]
          simp [iterate_add_apply]⟩
#align function.periodic_orbit_apply_iterate_eq Function.periodicOrbit_apply_iterate_eq
-/

#print Function.periodicOrbit_apply_eq /-
theorem periodicOrbit_apply_eq (hx : x ∈ periodicPts f) :
    periodicOrbit f (f x) = periodicOrbit f x :=
  periodicOrbit_apply_iterate_eq hx 1
#align function.periodic_orbit_apply_eq Function.periodicOrbit_apply_eq
-/

#print Function.periodicOrbit_chain /-
theorem periodicOrbit_chain (r : α → α → Prop) {f : α → α} {x : α} :
    (periodicOrbit f x).Chain r ↔ ∀ n < minimalPeriod f x, r ((f^[n]) x) ((f^[n + 1]) x) :=
  by
  by_cases hx : x ∈ periodic_pts f
  · have hx' := minimal_period_pos_of_mem_periodic_pts hx
    have hM := Nat.sub_add_cancel (succ_le_iff.2 hx')
    rw [periodic_orbit, ← Cycle.map_coe, Cycle.chain_map, ← hM, Cycle.chain_range_succ]
    refine' ⟨_, fun H => ⟨_, fun m hm => H _ (hm.trans (Nat.lt_succ_self _))⟩⟩
    · rintro ⟨hr, H⟩ n hn
      cases' eq_or_lt_of_le (lt_succ_iff.1 hn) with hM' hM'
      · rwa [hM', hM, iterate_minimal_period]
      · exact H _ hM'
    · rw [iterate_zero_apply]
      nth_rw 3 [← @iterate_minimal_period α f x]
      nth_rw 2 [← hM]
      exact H _ (Nat.lt_succ_self _)
  · rw [periodic_orbit_eq_nil_of_not_periodic_pt hx, minimal_period_eq_zero_of_nmem_periodic_pts hx]
    simp
#align function.periodic_orbit_chain Function.periodicOrbit_chain
-/

#print Function.periodicOrbit_chain' /-
theorem periodicOrbit_chain' (r : α → α → Prop) {f : α → α} {x : α} (hx : x ∈ periodicPts f) :
    (periodicOrbit f x).Chain r ↔ ∀ n, r ((f^[n]) x) ((f^[n + 1]) x) :=
  by
  rw [periodic_orbit_chain r]
  refine' ⟨fun H n => _, fun H n _ => H n⟩
  rw [iterate_succ_apply, ← iterate_mod_minimal_period_eq]
  nth_rw 2 [← iterate_mod_minimal_period_eq]
  rw [← iterate_succ_apply, minimal_period_apply hx]
  exact H _ (mod_lt _ (minimal_period_pos_of_mem_periodic_pts hx))
#align function.periodic_orbit_chain' Function.periodicOrbit_chain'
-/

end Function

namespace MulAction

open Function

variable {α β : Type _} [Group α] [MulAction α β] {a : α} {b : β}

#print MulAction.pow_smul_eq_iff_minimalPeriod_dvd /-
@[to_additive]
theorem pow_smul_eq_iff_minimalPeriod_dvd {n : ℕ} :
    a ^ n • b = b ↔ Function.minimalPeriod ((· • ·) a) b ∣ n := by
  rw [← is_periodic_pt_iff_minimal_period_dvd, is_periodic_pt, is_fixed_pt, smul_iterate]
#align mul_action.pow_smul_eq_iff_minimal_period_dvd MulAction.pow_smul_eq_iff_minimalPeriod_dvd
#align add_action.nsmul_vadd_eq_iff_minimal_period_dvd AddAction.nsmul_vadd_eq_iff_minimalPeriod_dvd
-/

/- warning: mul_action.zpow_smul_eq_iff_minimal_period_dvd -> MulAction.zpow_smul_eq_iff_minimalPeriod_dvd is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} [_inst_1 : Group.{u1} α] [_inst_2 : MulAction.{u1, u2} α β (DivInvMonoid.toMonoid.{u1} α (Group.toDivInvMonoid.{u1} α _inst_1))] {a : α} {b : β} {n : Int}, Iff (Eq.{succ u2} β (SMul.smul.{u1, u2} α β (MulAction.toHasSmul.{u1, u2} α β (DivInvMonoid.toMonoid.{u1} α (Group.toDivInvMonoid.{u1} α _inst_1)) _inst_2) (HPow.hPow.{u1, 0, u1} α Int α (instHPow.{u1, 0} α Int (DivInvMonoid.Pow.{u1} α (Group.toDivInvMonoid.{u1} α _inst_1))) a n) b) b) (Dvd.Dvd.{0} Int (semigroupDvd.{0} Int Int.semigroup) ((fun (a : Type) (b : Type) [self : HasLiftT.{1, 1} a b] => self.0) Nat Int (HasLiftT.mk.{1, 1} Nat Int (CoeTCₓ.coe.{1, 1} Nat Int (coeBase.{1, 1} Nat Int Int.hasCoe))) (Function.minimalPeriod.{u2} β (SMul.smul.{u1, u2} α β (MulAction.toHasSmul.{u1, u2} α β (DivInvMonoid.toMonoid.{u1} α (Group.toDivInvMonoid.{u1} α _inst_1)) _inst_2) a) b)) n)
but is expected to have type
  forall {α : Type.{u1}} {β : Type.{u2}} [_inst_1 : Group.{u1} α] [_inst_2 : MulAction.{u1, u2} α β (DivInvMonoid.toMonoid.{u1} α (Group.toDivInvMonoid.{u1} α _inst_1))] {a : α} {b : β} {n : Int}, Iff (Eq.{succ u2} β (HSMul.hSMul.{u1, u2, u2} α β β (instHSMul.{u1, u2} α β (MulAction.toSMul.{u1, u2} α β (DivInvMonoid.toMonoid.{u1} α (Group.toDivInvMonoid.{u1} α _inst_1)) _inst_2)) (HPow.hPow.{u1, 0, u1} α Int α (instHPow.{u1, 0} α Int (DivInvMonoid.Pow.{u1} α (Group.toDivInvMonoid.{u1} α _inst_1))) a n) b) b) (Dvd.dvd.{0} Int Int.instDvdInt (Nat.cast.{0} Int instNatCastInt (Function.minimalPeriod.{u2} β ((fun (x._@.Mathlib.Dynamics.PeriodicPts._hyg.6837 : α) (x._@.Mathlib.Dynamics.PeriodicPts._hyg.6839 : β) => HSMul.hSMul.{u1, u2, u2} α β β (instHSMul.{u1, u2} α β (MulAction.toSMul.{u1, u2} α β (DivInvMonoid.toMonoid.{u1} α (Group.toDivInvMonoid.{u1} α _inst_1)) _inst_2)) x._@.Mathlib.Dynamics.PeriodicPts._hyg.6837 x._@.Mathlib.Dynamics.PeriodicPts._hyg.6839) a) b)) n)
Case conversion may be inaccurate. Consider using '#align mul_action.zpow_smul_eq_iff_minimal_period_dvd MulAction.zpow_smul_eq_iff_minimalPeriod_dvdₓ'. -/
@[to_additive]
theorem zpow_smul_eq_iff_minimalPeriod_dvd {n : ℤ} :
    a ^ n • b = b ↔ (Function.minimalPeriod ((· • ·) a) b : ℤ) ∣ n :=
  by
  cases n
  · rw [Int.ofNat_eq_coe, zpow_ofNat, Int.coe_nat_dvd, pow_smul_eq_iff_minimal_period_dvd]
  ·
    rw [Int.negSucc_coe, zpow_neg, zpow_ofNat, inv_smul_eq_iff, eq_comm, dvd_neg, Int.coe_nat_dvd,
      pow_smul_eq_iff_minimal_period_dvd]
#align mul_action.zpow_smul_eq_iff_minimal_period_dvd MulAction.zpow_smul_eq_iff_minimalPeriod_dvd
#align add_action.zsmul_vadd_eq_iff_minimal_period_dvd AddAction.zsmul_vadd_eq_iff_minimalPeriod_dvd

variable (a b)

#print MulAction.pow_smul_mod_minimalPeriod /-
@[simp, to_additive]
theorem pow_smul_mod_minimalPeriod (n : ℕ) :
    a ^ (n % Function.minimalPeriod ((· • ·) a) b) • b = a ^ n • b := by
  conv_rhs =>
    rw [← Nat.mod_add_div n (minimal_period ((· • ·) a) b), pow_add, mul_smul,
      pow_smul_eq_iff_minimal_period_dvd.mpr (dvd_mul_right _ _)]
#align mul_action.pow_smul_mod_minimal_period MulAction.pow_smul_mod_minimalPeriod
#align add_action.nsmul_vadd_mod_minimal_period AddAction.nsmul_vadd_mod_minimalPeriod
-/

#print MulAction.zpow_smul_mod_minimalPeriod /-
@[simp, to_additive]
theorem zpow_smul_mod_minimalPeriod (n : ℤ) :
    a ^ (n % (Function.minimalPeriod ((· • ·) a) b : ℤ)) • b = a ^ n • b := by
  conv_rhs =>
    rw [← Int.emod_add_ediv n (minimal_period ((· • ·) a) b), zpow_add, mul_smul,
      zpow_smul_eq_iff_minimal_period_dvd.mpr (dvd_mul_right _ _)]
#align mul_action.zpow_smul_mod_minimal_period MulAction.zpow_smul_mod_minimalPeriod
#align add_action.zsmul_vadd_mod_minimal_period AddAction.zsmul_vadd_mod_minimalPeriod
-/

end MulAction


namespace fol_08

variable A : Type
variable f : A → A
variable P : A → Prop

variable  h : ∀ x, P x → P (f x)
include h

theorem fol_08 : ∀ y, P y → P (f (f y)) :=
assume y (h1: P y),
have h2: P y → P (f y), from h y,
have h3: P (f y), from h2 h1,
have h4: P (f y) → P (f (f y)), from h (f y),
show P (f (f y)), from h4 h3

end fol_08


import category_theory.limits.constructions.over
import category_theory.closed.cartesian
import category_theory.over

import topos

open category_theory category_theory.category category_theory.limits category_theory.cartesian_closed

/-!
# Over category

Over categories are toposes if the base is
From [MM92, IV.7]
-/

universes v u

noncomputable theory

variables {C : Type u} [category.{v} C] [topos C]

variables {b : C}

namespace equalizer

open category_theory.over

def hom_mk' (f g : over b) (u : f.left ⟶ g.left) (comm : u ≫ g.hom = f.hom) : (f ⟶ g) :=
@over.hom_mk _ _ _ f g u (by { rw comm, apply_auto_param })

lemma hom_mk_left' {f g : over b} {u : f.left ⟶ g.left} {comm : u ≫ g.hom = f.hom} :
  (hom_mk' f g u comm).left = u := by { unfold hom_mk', rw hom_mk_left }

-- lemma hom_mk_w' {f g : over b} {u : f ⟶ g} : u.left ≫ g.hom = f.hom := w _

variables {f g : over b} {u v : f ⟶ g}

def over_fork_of_fork (m : fork u.left v.left) : fork u v :=
fork.of_ι (hom_mk' (over.mk (m.ι ≫ f.hom)) _ m.ι (by rw mk_hom))
begin
ext, rw [comp_left, hom_mk_left', m.condition], refl
end

def over_fork_of_fork_X_hom (m : fork u.left v.left) : (over_fork_of_fork m).X.hom = m.ι ≫ f.hom :=
rfl

def over_fork_of_fork_ι_left (m : fork u.left v.left) : (over_fork_of_fork m).ι.left = m.ι :=
rfl

def fork_of_over_fork (m : fork u v) : fork u.left v.left :=
fork.of_ι m.ι.left (by { rw [←comp_left, m.condition, comp_left] })

def fork_of_over_fork_ι (m : fork u v) : (fork_of_over_fork m).ι = m.ι.left := rfl

def over_fork_lift {m : fork u.left v.left} (m_lim : is_limit m) (s : fork u v) :=
fork.is_limit.lift' m_lim (fork_of_over_fork s).ι (fork_of_over_fork s).condition

def over_fork_is_limit_of_fork_is_limit {m : fork u.left v.left} (m_lim : is_limit m) :
  is_limit (over_fork_of_fork m) :=
begin
  apply fork.is_limit.mk _ (λ s : fork u v, hom_mk' s.X (over_fork_of_fork m).X (over_fork_lift m_lim s).val
    begin
    erw [over_fork_of_fork_X_hom, ←assoc, (over_fork_lift m_lim s).prop, fork_of_over_fork_ι, w], refl
    end);
  intro s; simp only,
    ext1, erw [comp_left, (over_fork_lift m_lim s).prop], refl,
    intros t ht, ext1, apply fork.is_limit.hom_ext m_lim,
    erw fork.is_limit.lift_ι, rw [fork.ι_of_ι, fork_of_over_fork_ι, ←ht, comp_left],
    refl
end

instance : has_limit (parallel_pair u v) :=
has_limit.mk
  { cone := over_fork_of_fork (limit.cone (parallel_pair u.left v.left)),
    is_limit := over_fork_is_limit_of_fork_is_limit (limit.is_limit _) }

instance : has_equalizers (over b) := has_equalizers_of_has_limit_parallel_pair (over b)

end equalizer

instance wide_pullbacks : has_finite_wide_pullbacks C :=
has_finite_wide_pullbacks_of_has_finite_limits C

instance over_finite_limits : limits.has_finite_limits (over b) :=
category_theory.over.has_finite_limits


namespace prop_18

variables P Q : Prop

theorem prop_18 : ¬ (P ∧ Q) → ¬¬ (¬ P ∨ ¬ Q) :=
assume h1: ¬ (P ∧ Q),
assume h2: ¬(¬P ∨ ¬Q),
have h3: ¬ P ∨ ¬ Q, from or.elim
    (classical.em P)
    (assume h4: P,
     have h5: Q, from (classical.by_contradiction
        (assume h6: ¬ Q,
         h2 (or.inr h6))),
     have h7: P ∧ Q, from and.intro h4 h5,
     have h8: false, from h1 h7, classical.by_contradiction
        (assume h9: ¬(¬P ∨ ¬Q),
         h8))
        (assume h10: ¬ P,
         or.inl h10),
show false, from h2 h3

-- end namespace
end prop_18


import NBG.SetTheory.Axioms.Basic
import NBG.SetTheory.Relation.Equivalence

open Classical

def isSetLike (R : Class) [Relation R] : Prop :=
  ∀x: Class,∀_:Set x,
    isSet ((@Im (RelInv R) {x} ⟨RelInvRelationIsRelation⟩) ＼ {x})
class SetLike (R : Class) extends Relation R where
  isSetLike: isSetLike R

def isWellFounded (R : Class) [Relation R] : Prop :=
  ∀X:Class,∀_:NonEmptyClass X,
    ∃x:Class,∃hx:x∈X, ((((@Im (RelInv R) (@Singleton_mk x (Set.mk₁ hx)) ⟨RelInvRelationIsRelation⟩) ＼ (@Singleton_mk x (Set.mk₁ hx)))) ∩ X) ＝ ø
class WellFoundeRelation (R : Class) extends Relation R where
  isWellFounded: isWellFounded R

theorem InitIntervalExisits (R x : Class) [Relation R] [Set x]:
  ∃init_interval: Class, ∀z: Class,
    (z ∈ init_interval ↔ ∃_:Set z, ＜z,x＞ ∈ R) := sorry

noncomputable def InitInterval (R x : Class) [Relation R] [Set x]: Class :=
  choose (InitIntervalExisits R x)
noncomputable def InitInterval_def (R x : Class) [Relation R] [Set x]:
  ∀z:Class,
    (z ∈ (InitInterval R x) ↔ ∃_:Set z, ＜z,x＞ ∈ R) :=
  choose_spec (InitIntervalExisits R x)

theorem InitIntervalIsEmptyIf (R x : Class) [Relation R] [Set x]:
  (x ∉ Rng R) → isEmptyClass (InitInterval R x) := sorry

theorem InitEIntervalRelation' (x : Class) [Set x]:
  (@InitInterval E x ⟨EIsRelation⟩ _) ＝ (x＼{x}) := sorry

theorem InitEIntervalRelation (x : Class) [Set x]:
  (@InitInterval E x ⟨EIsRelation⟩ _) ＝ x := sorry

theorem InitEIntervalIsSetLike (x : Class) [Set x]:
  @isSetLike E ⟨EIsRelation⟩ := sorry

theorem WellFoundedIff (R x : Class) [TransitiveRelation R] [SetLike R] [Set x]:
  isWellFounded R ↔ (∀a,∀_:Set a,∀_:a⊂(Rng R),
    ∃y,∃hy:y∈a, ((@InitInterval R y _ (Set.mk₁ hy)) ∩ y) ＝ ø ) := sorry