/-
Copyright (c) 2022 Yury G. Kudryashov. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Yury G. Kudryashov
-/
import analysis.complex.cauchy_integral
import analysis.calculus.fderiv_analytic
import analysis.normed_space.completion

/-!
# Liouville's theorem

In this file we prove Liouville's theorem: if `f : E → F` is complex differentiable on the whole
space and its range is bounded, then the function is a constant. Various versions of this theorem
are formalized in `differentiable.apply_eq_apply_of_bounded`,
`differentiable.exists_const_forall_eq_of_bounded`, and
`differentiable.exists_eq_const_of_bounded`.

The proof is based on the Cauchy integral formula for the derivative of an analytic function, see
`complex.deriv_eq_smul_circle_integral`.
-/

open topological_space metric set filter asymptotics function measure_theory
open_locale topology filter nnreal real

universes u v
variables {E : Type u} [normed_add_comm_group E] [normed_space ℂ E]
  {F : Type v} [normed_add_comm_group F] [normed_space ℂ F]

local postfix `̂`:100 := uniform_space.completion

namespace complex

/-- If `f` is complex differentiable on an open disc with center `c` and radius `R > 0` and is
continuous on its closure, then `f' c` can be represented as an integral over the corresponding
circle.

TODO: add a version for `w ∈ metric.ball c R`.

TODO: add a version for higher derivatives. -/
lemma deriv_eq_smul_circle_integral [complete_space F] {R : ℝ} {c : ℂ} {f : ℂ → F} (hR : 0 < R)
  (hf : diff_cont_on_cl ℂ f (ball c R)) :
  deriv f c = (2 * π * I : ℂ)⁻¹ • ∮ z in C(c, R), (z - c) ^ (-2 : ℤ) • f z :=
begin
  lift R to ℝ≥0 using hR.le,
  refine (hf.has_fpower_series_on_ball hR).has_fpower_series_at.deriv.trans _,
  simp only [cauchy_power_series_apply, one_div, zpow_neg, pow_one, smul_smul, zpow_two, mul_inv]
end

lemma norm_deriv_le_aux [complete_space F] {c : ℂ} {R C : ℝ} {f : ℂ → F} (hR : 0 < R)
  (hf : diff_cont_on_cl ℂ f (ball c R)) (hC : ∀ z ∈ sphere c R, ‖f z‖ ≤ C) :
  ‖deriv f c‖ ≤ C / R :=
begin
  have : ∀ z ∈ sphere c R, ‖(z - c) ^ (-2 : ℤ) • f z‖ ≤ C / (R * R),
    from λ z (hz : abs (z - c) = R), by simpa [-mul_inv_rev, norm_smul, hz, zpow_two,
      ←div_eq_inv_mul] using (div_le_div_right (mul_pos hR hR)).2 (hC z hz),
  calc ‖deriv f c‖ = ‖(2 * π * I : ℂ)⁻¹ • ∮ z in C(c, R), (z - c) ^ (-2 : ℤ) • f z‖ :
    congr_arg norm (deriv_eq_smul_circle_integral hR hf)
  ... ≤ R * (C / (R * R)) :
    circle_integral.norm_two_pi_I_inv_smul_integral_le_of_norm_le_const hR.le this
  ... = C / R : by rw [mul_div_left_comm, div_self_mul_self', div_eq_mul_inv]
end

/-- If `f` is complex differentiable on an open disc of radius `R > 0`, is continuous on its
closure, and its values on the boundary circle of this disc are bounded from above by `C`, then the
norm of its derivative at the center is at most `C / R`. -/
lemma norm_deriv_le_of_forall_mem_sphere_norm_le {c : ℂ} {R C : ℝ} {f : ℂ → F} (hR : 0 < R)
  (hd : diff_cont_on_cl ℂ f (ball c R)) (hC : ∀ z ∈ sphere c R, ‖f z‖ ≤ C) :
  ‖deriv f c‖ ≤ C / R :=
begin
  set e : F →L[ℂ] F̂ := uniform_space.completion.to_complL,
  have : has_deriv_at (e ∘ f) (e (deriv f c)) c,
    from e.has_fderiv_at.comp_has_deriv_at c
      (hd.differentiable_at is_open_ball $ mem_ball_self hR).has_deriv_at,
  calc ‖deriv f c‖ = ‖deriv (e ∘ f) c‖ :
    by { rw this.deriv, exact (uniform_space.completion.norm_coe _).symm }
  ... ≤ C / R :
    norm_deriv_le_aux hR (e.differentiable.comp_diff_cont_on_cl hd)
      (λ z hz, (uniform_space.completion.norm_coe _).trans_le (hC z hz))
end

/-- An auxiliary lemma for Liouville's theorem `differentiable.apply_eq_apply_of_bounded`. -/
lemma liouville_theorem_aux {f : ℂ → F} (hf : differentiable ℂ f)
  (hb : bounded (range f)) (z w : ℂ) : f z = f w :=
begin
  suffices : ∀ c, deriv f c = 0, from is_const_of_deriv_eq_zero hf this z w,
  clear z w, intro c,
  obtain ⟨C, C₀, hC⟩ : ∃ C > (0 : ℝ), ∀ z, ‖f z‖ ≤ C,
  { rcases bounded_iff_forall_norm_le.1 hb with ⟨C, hC⟩,
    exact ⟨max C 1, lt_max_iff.2 (or.inr zero_lt_one),
      λ z, (hC (f z) (mem_range_self _)).trans (le_max_left _ _)⟩ },
  refine norm_le_zero_iff.1 (le_of_forall_le_of_dense $ λ ε ε₀, _),
  calc ‖deriv f c‖ ≤ C / (C / ε) :
    norm_deriv_le_of_forall_mem_sphere_norm_le (div_pos C₀ ε₀) hf.diff_cont_on_cl (λ z _, hC z)
  ... = ε : div_div_cancel' C₀.lt.ne'
end

end complex

namespace differentiable

open complex

/-- **Liouville's theorem**: a complex differentiable bounded function `f : E → F` is a constant. -/
lemma apply_eq_apply_of_bounded {f : E → F} (hf : differentiable ℂ f) (hb : bounded (range f))
  (z w : E) : f z = f w :=
begin
  set g : ℂ → F := f ∘ (λ t : ℂ, t • (w - z) + z),
  suffices : g 0 = g 1, by simpa [g],
  apply liouville_theorem_aux,
  exacts [hf.comp ((differentiable_id.smul_const (w - z)).add_const z),
    hb.mono (range_comp_subset_range _ _)]
end

/-- **Liouville's theorem**: a complex differentiable bounded function is a constant. -/
lemma exists_const_forall_eq_of_bounded {f : E → F} (hf : differentiable ℂ f)
  (hb : bounded (range f)) : ∃ c, ∀ z, f z = c :=
⟨f 0, λ z, hf.apply_eq_apply_of_bounded hb _ _⟩

/-- **Liouville's theorem**: a complex differentiable bounded function is a constant. -/
lemma exists_eq_const_of_bounded {f : E → F} (hf : differentiable ℂ f)
  (hb : bounded (range f)) : ∃ c, f = const E c :=
(hf.exists_const_forall_eq_of_bounded hb).imp $ λ c, funext

end differentiable


import normalizer_world.normalizer_is_normal -- hide

variables {G : Type} [group G] -- hide
/- Symbol:
⊆ : \subseteq
-/
/- Lemma: no-side-bar
If $H \leq K$ is a normal subgroup, then $K \leq N_G(H)$.
-/
lemma sn3 (H K : set G) [subgroup H]  [subgroup K]  (hHK : is_normal H K) :
K ⊆ normalizer H :=
begin
  intros k hk,
  intro x,
  cases hHK,
  split,
  {
    intro hx,
    specialize hHK_right k⁻¹ x,
    rw subgroup.inv_mem at hk,
    rw show k * x * k⁻¹ = k⁻¹⁻¹ * x * k⁻¹, by group,
    apply hHK_right hk hx,
  },
  {
    intro hx,
    specialize hHK_right k (k * x * k⁻¹) hk hx,
    rw show x = k⁻¹ * (k * x * k⁻¹) * k, by group,
    assumption,
  }

end


import ..phys_dimensions
import linear_algebra.affine_space.basic
--import ...math.affnKcoord.affnKcoord_std
import ...math.euclidnK.euclidnK_definitions

import ..scalar

open_locale affine

section foo

universes u

abbreviation time_frame := fm scalar 1 LENGTH
abbreviation time_space (f : time_frame) := spc scalar f
noncomputable def time_std_frame : time_frame := fm.base 1 LENGTH
noncomputable def time_std_space : time_space time_std_frame := mk_space (time_std_frame)

-- points in time
structure time {f : time_frame} (s : spc _ f ) extends point s
@[ext] lemma time.ext : ∀  {f : time_frame} {s : time_space f } (x y : time s),
    x.to_point = y.to_point → x = y :=
    begin
        intros f s x y e,
        cases x,
        cases y,
        simp *,
        have h₁ : ({to_point := x} : time s).to_point = x := rfl,
        simp [h₁] at e,
        exact e
    end

noncomputable def time.coord {f : time_frame} {s : time_space f } (t :time s): scalar :=
    (t.to_point.coords 0).coord

@[simp]
def mk_time' {f : time_frame} (s : time_space f ) (p : point s) : time s := time.mk p
@[simp]
noncomputable def mk_time {f : time_frame} (s : time_space f ) (k : scalar) : time s := time.mk (mk_point s ⟨[k],rfl⟩)

noncomputable instance {f : time_frame} (s : time_space f ): inhabited (time s) := ⟨mk_time _ 0⟩

-- intervals in time
structure duration {f : time_frame} (s : time_space f ) extends vectr s
@[ext] lemma duration.ext : ∀  {f : time_frame} {s : time_space f } (x y : duration s),
    x.to_vectr = y.to_vectr → x = y :=
    begin
        intros f s x y e,
        cases x,
        cases y,
        simp *,
        have h₁ : ({to_vectr := x} : duration s).to_vectr = x := rfl,
        simp [h₁] at e,
        exact e
    end

noncomputable def duration.coord {f : time_frame} {s : time_space f } (d :duration s): scalar :=
    (d.to_vectr.coords 0).coord

@[simp]
def mk_duration' {f : time_frame} (s : time_space f ) (v : vectr s) : duration s := duration.mk v
@[simp]
noncomputable def mk_duration  {f : time_frame} (s : time_space f ) (k : scalar) : duration s := duration.mk (mk_vectr s ⟨[k],rfl⟩)

noncomputable instance {f : time_frame} (s : time_space f ): inhabited (duration s) := ⟨mk_duration _ 0⟩

@[simp]
noncomputable def mk_time_frame {parent : time_frame} {s : spc scalar parent} (p : time s) (v : duration s) :=
mk_frame p.to_point (vectr_basis.mk (λi, v.to_vectr) sorry sorry)   -- TODO: make sure v ≠ 0

-- Public
@[simp]
noncomputable def mk_time_space (fr : time_frame) : time_space _ := mk_space fr

end foo

section bar

/-
    *************************************
    Instantiate module scalar (vector scalar)
    *************************************
-/

namespace time
variables {f : time_frame} {s : time_space f }
@[simp]
noncomputable def add_duration_duration (v1 v2 : duration s) : duration s :=
    mk_duration' s (v1.to_vectr + v2.to_vectr)
@[simp]
noncomputable def smul_duration (k : scalar) (v : duration s) : duration s :=
    mk_duration' s (k • v.to_vectr)
@[simp]
noncomputable def neg_duration (v : duration s) : duration s :=
    mk_duration' s ((-1 : scalar) • v.to_vectr)
@[simp]
noncomputable def sub_duration_duration (v1 v2 : duration s) : duration s :=    -- v1-v2
    add_duration_duration v1 (neg_duration v2)

noncomputable instance has_add_duration : has_add (duration s) := ⟨ add_duration_duration ⟩
lemma add_assoc_duration : ∀ a b c : duration s, a + b + c = a + (b + c) := begin
    intros,
    ext,
    dsimp only [has_add.add],
    dsimp only [add_duration_duration, mk_duration', has_add.add],
    dsimp only [add_vectr_vectr, mk_vectr', has_add.add],
    dsimp only [add_vec_vec],
    simp only [add_assoc],
end
noncomputable instance add_semigroup_duration : add_semigroup (duration s) := ⟨ add_duration_duration, add_assoc_duration⟩
@[simp]
noncomputable def duration_zero  := mk_duration s 0
noncomputable instance has_zero_duration : has_zero (duration s) := ⟨duration_zero⟩

lemma zero_add_duration : ∀ a : duration s, 0 + a = a :=
begin
    intros,
    ext,
    dsimp only [has_zero.zero, has_add.add],
    dsimp only [add_duration_duration, duration_zero, mk_duration', mk_duration, has_add.add],
    dsimp only [add_vectr_vectr, mk_vectr', mk_vectr, mk_vec_n, has_add.add],
    dsimp only [add_vec_vec, mk_vec, vector.nth],
    simp only [list.nth_le_singleton, zero_add],
end

lemma add_zero_duration : ∀ a : duration s, a + 0 = a :=
begin
    intros,
    ext,
    dsimp only [has_zero.zero, has_add.add],
    dsimp only [add_duration_duration, duration_zero, mk_duration', mk_duration, has_add.add],
    dsimp only [add_vectr_vectr, mk_vectr', mk_vectr, mk_vec_n, has_add.add],
    dsimp only [add_vec_vec, mk_vec, vector.nth],
    simp only [list.nth_le_singleton, add_zero],
end

@[simp]
noncomputable def nsmul_duration : ℕ → (duration s) → (duration s)
| nat.zero v := duration_zero
--| 1 v := v
| (nat.succ n) v := (add_duration_duration) v (nsmul_duration n v)

noncomputable instance add_monoid_duration : add_monoid (duration s) := ⟨
    -- add_semigroup
    add_duration_duration,
    add_assoc_duration,
    -- has_zero
    duration_zero,
    -- new structure
    @zero_add_duration f s,
    add_zero_duration,
    nsmul_duration
⟩

noncomputable instance has_neg_duration : has_neg (duration s) := ⟨neg_duration⟩
noncomputable instance has_sub_duration : has_sub (duration s) := ⟨ sub_duration_duration⟩
lemma sub_eq_add_neg_duration : ∀ a b : duration s, a - b = a + -b :=
begin
    intros,ext,
    refl,
end

noncomputable instance sub_neg_monoid_duration : sub_neg_monoid (duration s) :=
{
    neg := neg_duration,
    ..(show add_monoid (duration s), by apply_instance)
}
/-⟨
    add_duration_duration, add_assoc_duration, duration_zero,
    zero_add_duration,
    add_zero_duration, -- add_monoid
    neg_duration,                                                                  -- has_neg
    sub_duration_duration,                                                              -- has_sub
    sub_eq_add_neg_duration,                                                       -- new
⟩ -/

lemma add_left_neg_duration : ∀ a : duration s, -a + a = 0 :=
begin
    intros,
    ext,
    dsimp only [has_zero.zero, has_add.add, has_neg.neg],
    dsimp only [neg_duration, has_scalar.smul],
    dsimp only [add_duration_duration, smul_vectr, has_add.add, has_scalar.smul],
    dsimp only [add_vectr_vectr, smul_vec, mk_duration', mk_vectr', has_add.add],
    dsimp only [add_vec_vec],
    simp only [neg_mul_eq_neg_mul_symm, one_mul, mk_vectr, duration_zero, mk_duration, add_left_neg],
    dsimp only [mk_vec_n, mk_vec, vector.nth],
    simp only [list.nth_le_singleton]
end

noncomputable instance : add_group (duration s) := {
    add_left_neg := begin
        exact add_left_neg_duration,
    end,
..(show sub_neg_monoid (duration s), by apply_instance),

}

/--/ ⟨
    -- sub_neg_monoid
    add_duration_duration, add_assoc_duration, duration_zero, zero_add_duration, add_zero_duration, -- add_monoid
    neg_duration,                                                                  -- has_neg
    sub_duration_duration,                                                              -- has_sub
    sub_eq_add_neg_duration,
    -- new
    add_left_neg_duration,
⟩ -/

lemma add_comm_duration : ∀ a b : duration s, a + b = b + a :=
begin
    intros,
    ext,
    dsimp only [has_add.add],
    dsimp only [add_duration_duration, has_add.add],
    dsimp only [add_vectr_vectr, has_add.add],
    dsimp only [add_vec_vec, mk_duration', mk_vectr'],
    simp only [add_comm],
end
noncomputable instance add_comm_semigroup_duration : add_comm_semigroup (duration s) := ⟨
    -- add_semigroup
    add_duration_duration,
    add_assoc_duration,
    add_comm_duration,
⟩

noncomputable instance add_comm_monoid_duration : add_comm_monoid (duration s) := {
    add_comm := begin
        exact add_comm_duration
    end,
    ..(show add_monoid (duration s), by apply_instance)
}

/-⟨
-- add_monoid
    -- add_semigroup
    add_duration_duration,
    add_assoc_duration,
    -- has_zero
    duration_zero,
    -- new structure
    zero_add_duration,
    add_zero_duration,
-- add_comm_semigroup (minus repeats)
    add_comm_duration,
⟩-/

noncomputable instance has_scalar_duration : has_scalar scalar (duration s) := ⟨
smul_duration,
⟩

lemma one_smul_duration : ∀ b : duration s, (1 : scalar) • b = b := begin
    intros,
    ext,
    dsimp only [has_scalar.smul],
    dsimp only [smul_duration, has_scalar.smul],
    dsimp only [smul_vectr, has_scalar.smul],
    dsimp only [smul_vec, mk_duration', mk_vectr'],
    simp only [one_mul],
end
lemma mul_smul_duration : ∀ (x y : scalar) (b : duration s), (x * y) • b = x • y • b :=
begin
    intros,
    cases b,
    ext,
    exact mul_assoc x y _,
end

noncomputable instance mul_action_duration : mul_action scalar (duration s) := ⟨
one_smul_duration,
mul_smul_duration,
⟩

lemma smul_add_duration : ∀(r : scalar) (x y : duration s), r • (x + y) = r • x + r • y := begin
    intros,
    ext,
    dsimp only [has_scalar.smul, has_add.add],
    dsimp only [smul_duration, add_duration_duration, has_scalar.smul, has_add.add],
    dsimp only [smul_vectr, add_vectr_vectr, has_scalar.smul, has_add.add],
    dsimp only [smul_vec, add_vec_vec, mk_duration', mk_vectr'],
    simp only [distrib.left_distrib],
    refl,
end
lemma smul_zero_duration : ∀(r : scalar), r • (0 : duration s) = 0 := begin
    intros,
    ext,
    dsimp only [has_scalar.smul, has_zero.zero],
    dsimp only [smul_duration, duration_zero, has_scalar.smul],
    dsimp only [smul_vectr, has_scalar.smul],
    dsimp only [smul_vec, mk_duration', mk_vectr', mk_duration, mk_vectr, mk_vec_n, mk_vec, vector.nth],
    simp only [list.nth_le_singleton, mul_zero],
end
noncomputable instance distrib_mul_action_K_duration : distrib_mul_action scalar (duration s) := ⟨
smul_add_duration,
smul_zero_duration,
⟩

lemma add_smul_duration : ∀ (a b : scalar) (x : duration s), (a + b) • x = a • x + b • x :=
begin
  intros,
  ext,
  exact right_distrib _ _ _,
end
lemma zero_smul_duration : ∀ (x : duration s), (0 : scalar) • x = 0 := begin
    intros,
    ext,
    dsimp only [has_scalar.smul, has_zero.zero],
    dsimp only [smul_duration, duration_zero, has_scalar.smul],
    dsimp only [smul_vectr, has_scalar.smul],
    dsimp only [smul_vec, mk_duration', mk_vectr', mk_duration, mk_vectr, mk_vec_n, mk_vec, vector.nth],
    simp only [list.nth_le_singleton, mul_eq_zero],
    apply or.inl,
    refl,
end
noncomputable instance module_K_duration : module scalar (duration s) := ⟨ add_smul_duration, zero_smul_duration ⟩

noncomputable instance add_comm_group_duration : add_comm_group (duration s) :=
{
    add_comm := begin
        exact add_comm_duration

        /-intros,
        ext,
        let h0 : (a + b).to_vec = a.to_vec + b.to_vec := rfl,
        let h1 : (b + a).to_vec = b.to_vec + a.to_vec := rfl,
        rw [h0,h1],
        exact add_comm _ _,
        exact add_comm _ _,-/
    end,
--to_add_group := (show add_group (vec K), by apply_instance),
--to_add_comm_monoid := (show add_comm_monoid (vec K), by apply_instance),
..(show add_group (duration s), by apply_instance)
}
/-⟨
-- add_group
    add_duration_duration, add_assoc_duration, duration_zero, zero_add_duration, add_zero_duration, -- add_monoid
    neg_duration,                                                                  -- has_neg
    sub_duration_duration,                                                              -- has_sub
    sub_eq_add_neg_duration,
    add_left_neg_duration,
-- commutativity
    add_comm_duration,
⟩-/

noncomputable instance : module scalar (duration s) := @time.module_K_duration f s

/-
    ********************
    *** Affine space ***
    ********************
-/

/-
Affine operations
-/
noncomputable instance : has_add (duration s) := ⟨add_duration_duration⟩
noncomputable instance : has_zero (duration s) := ⟨duration_zero⟩
noncomputable instance : has_neg (duration s) := ⟨neg_duration⟩

@[simp]
noncomputable def sub_time_time {f : time_frame} {s : time_space f } (p1 p2 : time s) : duration s :=
    mk_duration' s (p1.to_point -ᵥ p2.to_point)
@[simp]
noncomputable def add_time_duration {f : time_frame} {s : time_space f } (p : time s) (v : duration s) : time s :=
    mk_time' s (v.to_vectr +ᵥ p.to_point) -- reorder assumes order is irrelevant
@[simp]
noncomputable def add_duration_time {f : time_frame} {s : time_space f } (v : duration s) (p : time s) : time s :=
    mk_time' s (v.to_vectr +ᵥ p.to_point)

noncomputable instance : has_vadd (duration s) (time s) := ⟨add_duration_time⟩

lemma zero_duration_vadd'_a1 : ∀ p : time s, (0 : duration s) +ᵥ p = p := begin
    intros,
    ext,
    dsimp only [has_vadd.vadd, has_zero.zero],
    dsimp only [add_duration_time, duration_zero, has_vadd.vadd],
    dsimp only [add_vectr_point, has_vadd.vadd],
    dsimp only [aff_vec_group_action, add_vec_pt, mk_time', mk_point', mk_duration, mk_vectr, mk_vec_n, mk_vec, vector.nth],
    simp only [list.nth_le_singleton, add_zero],
end
lemma duration_add_assoc'_a1 : ∀ (g1 g2 : duration s) (p : time s), g1 +ᵥ (g2 +ᵥ p) = (g1 + g2) +ᵥ p := begin
    intros,
    ext,
    dsimp only [has_add.add, has_vadd.vadd],
    dsimp only [add_duration_time, add_duration_duration, has_add.add, has_vadd.vadd],
    dsimp only [add_vectr_point, add_vectr_vectr, has_add.add, has_vadd.vadd],
    dsimp only [aff_vec_group_action, add_vec_vec, add_vec_pt, mk_time', mk_point', mk_duration', mk_vectr'],
    simp only [add_assoc, add_right_inj],
    simp only [add_comm],
end

noncomputable instance duration_add_action: add_action (duration s) (time s) :=
⟨ zero_duration_vadd'_a1,
begin
    let h0 := duration_add_assoc'_a1,
    intros,
    exact (h0 g₁ g₂ p).symm
end⟩

noncomputable instance time_has_vsub : has_vsub (duration s) (time s) := ⟨ sub_time_time⟩

instance : nonempty (time s) := ⟨mk_time s 0⟩

lemma time_vsub_vadd_a1 : ∀ (p1 p2 : (time s)), (p1 -ᵥ p2) +ᵥ p2 = p1 := begin
    intros,
    ext,
    dsimp only [has_vsub.vsub, has_vadd.vadd],
    dsimp only [add_duration_time, sub_time_time, has_vsub.vsub, has_vadd.vadd],
    dsimp only [add_vectr_point, aff_point_group_sub, sub_point_point, has_vsub.vsub, has_vadd.vadd],
    dsimp only [aff_vec_group_action, aff_point_group_sub, add_vec_pt, aff_pt_group_sub, sub_pt_pt, mk_time', mk_point', mk_duration', mk_vectr'],
    simp only [add_sub_cancel'_right],
end
lemma time_vadd_vsub_a1 : ∀ (g : duration s) (p : time s), g +ᵥ p -ᵥ p = g :=
begin
    intros, ext,
    repeat {
    have h0 : ((g +ᵥ p -ᵥ p) : duration s).to_vectr = (g.to_vectr +ᵥ p.to_point -ᵥ p.to_point) := rfl,
    rw h0,
    simp *,
    }

end

noncomputable instance aff_time_torsor : add_torsor (duration s) (time s) := ⟨
    begin
        exact time_vsub_vadd_a1,
    end,
    begin
        exact time_vadd_vsub_a1,
    end,

⟩

open_locale affine

noncomputable instance : affine_space (duration s) (time s) := @time.aff_time_torsor f s

end time -- ha ha
end bar

universes u

@[ext]
structure time_transform {f1 : time_frame} {f2 : time_frame} (sp1 : time_space f1) (sp2 : time_space f2)
  extends fm_tr sp1 sp2

noncomputable def time_space.mk_time_transform_to {f1 : time_frame} (s1 : time_space f1) : Π {f2 : time_frame} (s2 : time_space f2),
        time_transform s1 s2 := --(time s2) ≃ᵃ[scalar] (time s1) :=
    λ f2 s2,
        ⟨s1.fm_tr s2⟩

noncomputable def time_transform.symm
    {f1 : time_frame} {f2 : time_frame} {sp1 : time_space f1} {sp2 : time_space f2} (ttr : time_transform sp1 sp2)
    : time_transform sp2 sp1 := ⟨(ttr.1).symm⟩

noncomputable def time_transform.trans
    {f1 : time_frame} {f2 : time_frame} {f3 : time_frame} {sp1 : time_space f1} {sp2 : time_space f2} {sp3 : time_space f3}
    (ttr : time_transform sp1 sp2)
    : time_transform sp2 sp3 → time_transform sp1 sp3 := λttr_, ⟨(ttr.1).trans ttr_.1⟩

noncomputable def time_transform.transform_time
    {f1 : time_frame} {s1 : spc _ f1}
    {f2 : time_frame} {s2 : spc _ f2}
    (tr: time_transform s1 s2 ) : time s1 → time s2 :=
    λt : time s1,
    ⟨tr.to_fm_tr.to_equiv t.to_point⟩

noncomputable def time_transform.transform_duration
    {f1 : time_frame} {s1 : spc _ f1}
    {f2 : time_frame} {s2 : spc _ f2}
    (tr: time_transform s1 s2 ) : duration s1 → duration s2 :=
    λd,
    let as_pt : point s1 := ⟨λi, mk_pt scalar (d.coords i).coord⟩ in
    let tr_pt := (tr.to_equiv as_pt) in
    ⟨⟨λi, mk_vec scalar (tr_pt.coords i).coord⟩⟩


lemma mul_comm (a b : mynat) : a * b = b * a :=
begin
induction b with c hc,
simp,
rwa [mul_succ, succ_mul, hc],
end


/-
Copyright (c) 2014 Robert Lewis. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Robert Lewis, Leonardo de Moura, Johannes Hölzl, Mario Carneiro
-/
import algebra.field.defs
import algebra.group_with_zero.power
import algebra.parity

/-!
# Results about powers in fields or division rings.

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

This file exists to ensure we can define `field` with minimal imports,
so contains some lemmas about powers of elements which need imports
beyond those needed for the basic definition.
-/

variables {α : Type*}

section division_ring
variables [division_ring α] {n : ℤ}

@[simp] lemma zpow_bit1_neg (a : α) (n : ℤ) : (-a) ^ bit1 n = - a ^ bit1 n :=
by rw [zpow_bit1', zpow_bit1', neg_mul_neg, neg_mul_eq_mul_neg]

lemma odd.neg_zpow (h : odd n) (a : α) : (-a) ^ n = - a ^ n :=
by { obtain ⟨k, rfl⟩ := h.exists_bit1, exact zpow_bit1_neg _ _ }

lemma odd.neg_one_zpow (h : odd n) : (-1 : α) ^ n = -1 := by rw [h.neg_zpow, one_zpow]

end division_ring


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

! This file was ported from Lean 3 source module analysis.normed_space.lp_equiv
! leanprover-community/mathlib commit 6afc9b06856ad973f6a2619e3e8a0a8d537a58f2
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Analysis.NormedSpace.LpSpace
import Mathbin.Analysis.NormedSpace.PiLp
import Mathbin.Topology.ContinuousFunction.Bounded

/-!
# Equivalences among $L^p$ spaces

In this file we collect a variety of equivalences among various $L^p$ spaces.  In particular,
when `α` is a `fintype`, given `E : α → Type u` and `p : ℝ≥0∞`, there is a natural linear isometric
equivalence `lp_pi_Lpₗᵢ : lp E p ≃ₗᵢ pi_Lp p E`. In addition, when `α` is a discrete topological
space, the bounded continuous functions `α →ᵇ β` correspond exactly to `lp (λ _, β) ∞`. Here there
can be more structure, including ring and algebra structures, and we implement these equivalences
accordingly as well.

We keep this as a separate file so that the various $L^p$ space files don't import the others.

Recall that `pi_Lp` is just a type synonym for `Π i, E i` but given a different metric and norm
structure, although the topological, uniform and bornological structures coincide definitionally.
These structures are only defined on `pi_Lp` for `fintype α`, so there are no issues of convergence
to consider.

While `pre_lp` is also a type synonym for `Π i, E i`, it allows for infinite index types. On this
type there is a predicate `mem_ℓp` which says that the relevant `p`-norm is finite and `lp E p` is
the subtype of `pre_lp` satisfying `mem_ℓp`.

## TODO

* Equivalence between `lp` and `measure_theory.Lp`, for `f : α → E` (i.e., functions rather than
  pi-types) and the counting measure on `α`

-/

open ENNReal

section LpPiLp

variable {α : Type _} {E : α → Type _} [∀ i, NormedAddCommGroup (E i)] {p : ℝ≥0∞}

/-- When `α` is `finite`, every `f : pre_lp E p` satisfies `mem_ℓp f p`. -/
theorem Memℓp.all [Finite α] (f : ∀ i, E i) : Memℓp f p :=
  by
  rcases p.trichotomy with (rfl | rfl | h)
  · exact mem_ℓp_zero_iff.mpr { i : α | f i ≠ 0 }.toFinite
  · exact mem_ℓp_infty_iff.mpr (Set.Finite.bddAbove (Set.range fun i : α => ‖f i‖).toFinite)
  · cases nonempty_fintype α
    exact memℓp_gen ⟨finset.univ.sum _, hasSum_fintype _⟩
#align mem_ℓp.all Memℓp.all

variable [Fintype α]

/-- The canonical `equiv` between `lp E p ≃ pi_Lp p E` when `E : α → Type u` with `[fintype α]`. -/
def Equiv.lpPiLp : lp E p ≃ PiLp p E where
  toFun f := f
  invFun f := ⟨f, Memℓp.all f⟩
  left_inv f := lp.ext <| funext fun x => rfl
  right_inv f := funext fun x => rfl
#align equiv.lp_pi_Lp Equiv.lpPiLp

theorem coe_equiv_lpPiLp (f : lp E p) : Equiv.lpPiLp f = f :=
  rfl
#align coe_equiv_lp_pi_Lp coe_equiv_lpPiLp

theorem coe_equiv_lpPiLp_symm (f : PiLp p E) : (Equiv.lpPiLp.symm f : ∀ i, E i) = f :=
  rfl
#align coe_equiv_lp_pi_Lp_symm coe_equiv_lpPiLp_symm

theorem equiv_lpPiLp_norm (f : lp E p) : ‖Equiv.lpPiLp f‖ = ‖f‖ :=
  by
  rcases p.trichotomy with (rfl | rfl | h)
  · rw [PiLp.norm_eq_card, lp.norm_eq_card_dsupport]
    rfl
  · rw [PiLp.norm_eq_csupr, lp.norm_eq_csupr]
    rfl
  · rw [PiLp.norm_eq_sum h, lp.norm_eq_tsum_rpow h, tsum_fintype]
    rfl
#align equiv_lp_pi_Lp_norm equiv_lpPiLp_norm

/-- The canonical `add_equiv` between `lp E p` and `pi_Lp p E` when `E : α → Type u` with
`[fintype α]` and `[fact (1 ≤ p)]`. -/
def AddEquiv.lpPiLp [Fact (1 ≤ p)] : lp E p ≃+ PiLp p E :=
  { Equiv.lpPiLp with map_add' := fun f g => rfl }
#align add_equiv.lp_pi_Lp AddEquiv.lpPiLp

theorem coe_addEquiv_lpPiLp [Fact (1 ≤ p)] (f : lp E p) : AddEquiv.lpPiLp f = f :=
  rfl
#align coe_add_equiv_lp_pi_Lp coe_addEquiv_lpPiLp

theorem coe_addEquiv_lpPiLp_symm [Fact (1 ≤ p)] (f : PiLp p E) :
    (AddEquiv.lpPiLp.symm f : ∀ i, E i) = f :=
  rfl
#align coe_add_equiv_lp_pi_Lp_symm coe_addEquiv_lpPiLp_symm

section Equivₗᵢ

variable (𝕜 : Type _) [NontriviallyNormedField 𝕜] [∀ i, NormedSpace 𝕜 (E i)]

/-- The canonical `linear_isometry_equiv` between `lp E p` and `pi_Lp p E` when `E : α → Type u`
with `[fintype α]` and `[fact (1 ≤ p)]`. -/
noncomputable def lpPiLpₗᵢ [Fact (1 ≤ p)] : lp E p ≃ₗᵢ[𝕜] PiLp p E :=
  { AddEquiv.lpPiLp with
    map_smul' := fun k f => rfl
    norm_map' := equiv_lpPiLp_norm }
#align lp_pi_Lpₗᵢ lpPiLpₗᵢ

variable {𝕜}

theorem coe_lpPiLpₗᵢ [Fact (1 ≤ p)] (f : lp E p) : lpPiLpₗᵢ 𝕜 f = f :=
  rfl
#align coe_lp_pi_Lpₗᵢ coe_lpPiLpₗᵢ

theorem coe_lpPiLpₗᵢ_symm [Fact (1 ≤ p)] (f : PiLp p E) : ((lpPiLpₗᵢ 𝕜).symm f : ∀ i, E i) = f :=
  rfl
#align coe_lp_pi_Lpₗᵢ_symm coe_lpPiLpₗᵢ_symm

end Equivₗᵢ

end LpPiLp

section LpBcf

open BoundedContinuousFunction

open BoundedContinuousFunction

-- note: `R` and `A` are explicit because otherwise Lean has elaboration problems
variable {α E : Type _} (R A 𝕜 : Type _) [TopologicalSpace α] [DiscreteTopology α]

variable [NormedRing A] [NormOneClass A] [NontriviallyNormedField 𝕜] [NormedAlgebra 𝕜 A]

variable [NormedAddCommGroup E] [NormedSpace 𝕜 E] [NonUnitalNormedRing R]

section NormedAddCommGroup

/-- The canonical map between `lp (λ (_ : α), E) ∞` and `α →ᵇ E` as an `add_equiv`. -/
noncomputable def AddEquiv.lpBcf : lp (fun _ : α => E) ∞ ≃+ (α →ᵇ E)
    where
  toFun f := ofNormedAddCommGroupDiscrete f ‖f‖ <| le_csupᵢ (memℓp_infty_iff.mp f.Prop)
  invFun f := ⟨f, f.bddAbove_range_norm_comp⟩
  left_inv f := lp.ext rfl
  right_inv f := ext fun x => rfl
  map_add' f g := ext fun x => rfl
#align add_equiv.lp_bcf AddEquiv.lpBcf

theorem coe_addEquiv_lpBcf (f : lp (fun _ : α => E) ∞) : (AddEquiv.lpBcf f : α → E) = f :=
  rfl
#align coe_add_equiv_lp_bcf coe_addEquiv_lpBcf

theorem coe_addEquiv_lpBcf_symm (f : α →ᵇ E) : (AddEquiv.lpBcf.symm f : α → E) = f :=
  rfl
#align coe_add_equiv_lp_bcf_symm coe_addEquiv_lpBcf_symm

/-- The canonical map between `lp (λ (_ : α), E) ∞` and `α →ᵇ E` as a `linear_isometry_equiv`. -/
noncomputable def lpBcfₗᵢ : lp (fun _ : α => E) ∞ ≃ₗᵢ[𝕜] α →ᵇ E :=
  { AddEquiv.lpBcf with
    map_smul' := fun k f => rfl
    norm_map' := fun f => by
      simp only [norm_eq_supr_norm, lp.norm_eq_csupr]
      rfl }
#align lp_bcfₗᵢ lpBcfₗᵢ

variable {𝕜}

theorem coe_lpBcfₗᵢ (f : lp (fun _ : α => E) ∞) : (lpBcfₗᵢ 𝕜 f : α → E) = f :=
  rfl
#align coe_lp_bcfₗᵢ coe_lpBcfₗᵢ

theorem coe_lpBcfₗᵢ_symm (f : α →ᵇ E) : ((lpBcfₗᵢ 𝕜).symm f : α → E) = f :=
  rfl
#align coe_lp_bcfₗᵢ_symm coe_lpBcfₗᵢ_symm

end NormedAddCommGroup

section RingAlgebra

/-- The canonical map between `lp (λ (_ : α), R) ∞` and `α →ᵇ R` as a `ring_equiv`. -/
noncomputable def RingEquiv.lpBcf : lp (fun _ : α => R) ∞ ≃+* (α →ᵇ R) :=
  { @AddEquiv.lpBcf _ R _ _ _ with map_mul' := fun f g => ext fun x => rfl }
#align ring_equiv.lp_bcf RingEquiv.lpBcf

variable {R}

theorem coe_ringEquiv_lpBcf (f : lp (fun _ : α => R) ∞) : (RingEquiv.lpBcf R f : α → R) = f :=
  rfl
#align coe_ring_equiv_lp_bcf coe_ringEquiv_lpBcf

theorem coe_ringEquiv_lpBcf_symm (f : α →ᵇ R) : ((RingEquiv.lpBcf R).symm f : α → R) = f :=
  rfl
#align coe_ring_equiv_lp_bcf_symm coe_ringEquiv_lpBcf_symm

variable (α)

-- even `α` needs to be explicit here for elaboration
-- the `norm_one_class A` shouldn't really be necessary, but currently it is for
-- `one_mem_ℓp_infty` to get the `ring` instance on `lp`.
/-- The canonical map between `lp (λ (_ : α), A) ∞` and `α →ᵇ A` as an `alg_equiv`. -/
noncomputable def AlgEquiv.lpBcf : lp (fun _ : α => A) ∞ ≃ₐ[𝕜] α →ᵇ A :=
  { RingEquiv.lpBcf A with commutes' := fun k => rfl }
#align alg_equiv.lp_bcf AlgEquiv.lpBcf

variable {α A 𝕜}

theorem coe_algEquiv_lpBcf (f : lp (fun _ : α => A) ∞) : (AlgEquiv.lpBcf α A 𝕜 f : α → A) = f :=
  rfl
#align coe_alg_equiv_lp_bcf coe_algEquiv_lpBcf

theorem coe_algEquiv_lpBcf_symm (f : α →ᵇ A) : ((AlgEquiv.lpBcf α A 𝕜).symm f : α → A) = f :=
  rfl
#align coe_alg_equiv_lp_bcf_symm coe_algEquiv_lpBcf_symm

end RingAlgebra

end LpBcf


def f (x : Nat) : Nat :=
match x with
| 30  => 31
| y+1 => y
| 0   => 10

#eval f 20
#eval f 0
#eval f 30

universe u
infix:50 " ≅ " => HEq
theorem ex1 {α : Sort u} {a b : α} (h : a ≅ b) : a = b :=
match α, a, b, h with
| _, _, _, HEq.refl _ => rfl

theorem ex2 {α : Sort u} {a b : α} (h : a ≅ b) : a = b :=
match a, b, h with
| _, _, HEq.refl _ => rfl

theorem ex3 {α : Sort u} {a b : α} (h : a ≅ b) : a = b :=
match b, h with
| _, HEq.refl _ => rfl

theorem ex4  {α β : Sort u} {b : β} {a a' : α} (h₁ : a = a') (h₂ : a' ≅ b) : a ≅ b :=
match β, a', b, h₁, h₂ with
| _, _, _, rfl, HEq.refl _ => HEq.refl _

theorem ex5  {α β : Sort u} {b : β} {a a' : α} (h₁ : a = a') (h₂ : a' ≅ b) : a ≅ b :=
match a', h₁, h₂ with
| _, rfl, h₂ => h₂

theorem ex6  {α β : Sort u} {b : β} {a a' : α} (h₁ : a = a') (h₂ : a' ≅ b) : a ≅ b :=
by {
  subst h₁;
  assumption
}

theorem ex7 (a : Bool) (p q : Prop) (h₁ : a = true → p) (h₂ : a = false → q) : p ∨ q :=
  match (generalizing := false) h:a with
  | true  => Or.inl $ h₁ h
  | false => Or.inr $ h₂ h

theorem ex7' (a : Bool) (p q : Prop) (h₁ : a = true → p) (h₂ : a = false → q) : p ∨ q :=
  match a with
  | true  => Or.inl $ h₁ rfl
  | false => Or.inr $ h₂ rfl

def head {α} (xs : List α) (h : xs = [] → False) : α :=
  match he:xs with
  | []   => by contradiction
  | x::_ => x

variable {α : Type u} {p : α → Prop}

theorem ex8 {a1 a2 : {x // p x}} (h : a1.val = a2.val) : a1 = a2 :=
match a1, a2, h with
| ⟨_, _⟩, ⟨_, _⟩, rfl => rfl

universe v
variable {β : α → Type v}

theorem ex9 {p₁ p₂ : Sigma (fun a => β a)} (h₁ : p₁.1 = p₂.1) (h : p₁.2 ≅ p₂.2) : p₁ = p₂ :=
match p₁, p₂, h₁, h with
| ⟨_, _⟩, ⟨_, _⟩, rfl, HEq.refl _ => rfl

inductive F : Nat → Type
| z : {n : Nat} → F (n+1)
| s : {n : Nat} → F n → F (n+1)

def f0 {α : Sort u} (x : F 0) : α :=
nomatch x

def f0' {α : Sort u} (x : F 0) : α :=
nomatch id x

def f1 {α : Sort u} (x : F 0 × Bool) : α :=
nomatch x

def f2 {α : Sort u} (x : Sum (F 0) (F 0)) : α :=
nomatch x

def f3 {α : Sort u} (x : Bool × F 0) : α :=
nomatch x

def f4 (x : Sum (F 0 × Bool) Nat) : Nat :=
match x with
| Sum.inr x => x

#eval f4 $ Sum.inr 100


import data.int.basic
import data.int.modeq
import data.nat.prime_norm_num
import data.zmod.basic

import tactic.linarith
import tactic.ring

/-
Indian Mathematical Olympiad 1998, problem 1

(a) Show that the product of two numbers of the form a² + 3b² is again of that form.
(b) If an integer n is such that 7n is of the form a² + 3b², prove that n is also of that form.
-/

theorem india1998_q1a (a₁ a₂ b₁ b₂ : ℤ) :
  (∃ a₃ b₃, (a₁^2 + 3 * b₁^2) * (a₂^2 + 3 * b₂^2) = (a₃^2 + 3 * b₃^2)) :=
⟨a₁ * a₂ + 3 * b₁ * b₂,
 a₁ * b₂ - b₁ * a₂,
 by ring⟩

lemma make_nonneg (a b: ℤ) (hb : 0 < b) : 0 ≤ a + b * ((b - a) / b) :=
by linarith[int.mod_lt_of_pos (b-a) hb, (b - a).mod_add_div b]

lemma lemma1' (a : ℤ) (b : ℕ) (hb : 0 < b) : ((a : zmod b).val : ℤ) = a % (b : ℤ) :=
begin
  have h1: (a : zmod b) = ((( a + b * ((b - a )/ b)) : ℤ): zmod b) := by simp,
  rw [←int.add_mul_mod_self_left a b ((b - a)/ b), h1],
  have h2 : 0 ≤ (( a + b * ((b - a )/ b)) : ℤ) := make_nonneg a ↑b (int.coe_nat_pos.mpr hb),
  obtain ⟨A, hA⟩ := int.eq_coe_of_zero_le h2,
  simp [hA, zmod.val_nat_cast A],
end

lemma lemma1 (a: ℤ) : ((a : zmod 7).val : ℤ) = a % 7 := lemma1' a 7 (by norm_num)

theorem india1998_q1b (n a b: ℤ) (hn : a^2 + 3 * b^2 = 7 * n) :
  (∃ a b : ℤ, a^2 + 3 * b^2 = n) :=
begin
  let az : zmod 7 := a,
  let bz : zmod 7 := b,

  have h1 := calc az ^ 2 + (-4) * bz ^ 2
        = az^2 + ((3:ℤ):zmod 7) * bz^2 : by ring
    ... = ((a^2 : ℤ) : zmod 7) + ((3:ℤ):zmod 7) * (((b^2) : ℤ) : zmod 7)
                                                            : by rw [int.cast_pow a 2, int.cast_pow b 2]
    ... = ((a^2 : ℤ) : zmod 7) + (((3 * b^2) : ℤ) : zmod 7) : by rw [int.cast_mul 3 (b^2)]
    ... = (((a^2 + 3 * b^2) : ℤ) : zmod 7) : (int.cast_add _ _).symm
    ... = (((7 * n) : ℤ) : zmod 7) : congr_arg coe hn
    ... = 0 : by {rw [int.cast_mul], exact zero_mul _},

  have h9: az ^ 2 + (-4) * bz ^ 2 + 4 * bz^2 = 0 + 4 * bz^2 := congr_fun (congr_arg has_add.add h1) _,
  rw [neg_mul, neg_add_cancel_right, fin.zero_add] at h9,

  have h10 : 4 * bz^2 = (2 * bz) ^ 2 := by ring,
  rw [h10] at h9,
  haveI : fact (nat.prime 7) := ⟨by norm_num⟩,

  obtain (hep : az = 2 * bz) | (hen : az = - (2 * bz)) := eq_or_eq_neg_of_sq_eq_sq _ _ h9,
  { have h11: (2 * az + 3 * bz) = 0,
    { rw [hep], ring_nf,
      have h12: (7 : zmod 7) = 0 := rfl,
      rw [h12],
      simp },
    have h13: 7 ∣ (2 * a + 3 * b),
    { have h50 : (2 * az + 3 * bz).val = (0 : zmod 7).val := congr_arg zmod.val h11,
      have h51 : (2 * az + 3 * bz).val = ((2 * az).val + (3 * bz).val) % 7 := zmod.val_add _ _,
      have h52 : (2 * az).val = (2 : zmod 7).val * az.val % 7 := zmod.val_mul _ _,
      have h53 : (3 * bz).val = (3 : zmod 7).val * bz.val % 7 := zmod.val_mul _ _,
      rw [h51, h52, h53, nat.add_mod_mod, nat.mod_add_mod, zmod.val_zero] at h50,

      have h52 : ((2 * (az.val:ℤ) + 3 * (bz.val:ℤ))) % 7 = 0,
      { norm_cast, exact h50},
      rw [lemma1 a, lemma1 b] at h52,
      rw [←int.mod_add_div a 7, ←int.mod_add_div b 7],
      have h53 : 2 * (a % 7 + 7 * (a / 7)) + 3 * (b % 7 + 7 * (b / 7)) =
              2 * (a % 7) + 3 * (b % 7) + 7 * (2 * (a / 7) + 3 * (b / 7)) := by ring,
      rw [h53],
      have h54 : 7 ∣ 7 * (2 * (a / 7) + 3 * (b / 7)) := dvd.intro _ rfl,

      exact dvd_add (int.dvd_of_mod_eq_zero h52) h54 },

    obtain ⟨m1, hm1⟩ := exists_eq_mul_right_of_dvd h13,

    have h15: (az + (- 2) * bz) = 0,
    { rw [hep], ring_nf },
    have h16: 7 ∣ (a + (-2) * b),
    { have h50 : (az + (-2) * bz).val = (0 : zmod 7).val := congr_arg zmod.val h15,
      have h51 : (az + (-2) * bz).val = (az.val + ((-2) * bz).val) % 7 := zmod.val_add _ _,
      have h53 : ((-2) * bz).val = (-2 : zmod 7).val * bz.val % 7 := zmod.val_mul _ _,
      rw [h51, h53, nat.add_mod_mod, zmod.val_zero] at h50,
      have h52 : (((az.val:ℤ) + 5 * (bz.val:ℤ))) % 7 = 0,
      { norm_cast, exact h50},
      rw [lemma1 a, lemma1 b] at h52,

      have h52' : 7 ∣ a % 7 + 5 * (b % 7) := int.dvd_of_mod_eq_zero h52,
      rw [←int.mod_add_div a 7, ←int.mod_add_div b 7],
      have h53 : a % 7 + 7 * (a / 7) + (-2) * (b % 7 + 7 * (b / 7)) =
              a % 7 + 5 * (b % 7) + 7 * (a / 7 - 2 * (b / 7) - (b % 7)) := by ring,
      rw [h53],
      have h54 : 7 ∣ 7 * (a / 7 - 2 * (b / 7) - (b % 7)) := dvd.intro _ rfl,
      exact dvd_add h52' h54 },
    obtain ⟨m2, hm2⟩ := exists_eq_mul_right_of_dvd h16,

    use m1,
    use m2,
    have h20 : (7 * m1) ^ 2 + 3 * (7 * m2) ^ 2 = 7 * 7 * n,
    { rw [←hm1, ←hm2],
      ring_nf,
      have h21 : (2 * a + 3 * b) ^ 2 + 3 * (a - 2 * b) ^ 2 = 7 * (a ^2 + 3 * b^2) := by ring,
      have h18: 7 * (a ^ 2 + 3 * b ^ 2) = 7 * (7 * n) := congr_arg (has_mul.mul 7) hn,
      ring_nf at h18,
      rw [h18] },

    have h21 : (7 * m1) ^ 2 + 3 * (7 * m2) ^ 2 = 7 * 7 * (m1 ^ 2 + 3 * m2 ^ 2) := by ring,

    rw[h21] at h20,
    have h22 : (7:ℤ) * 7 ≠ 0 := by norm_num,
    exact (mul_right_inj' h22).mp h20 },

  { have h11: (2 * az + (-3) * bz) = 0,
    { rw [hen], ring_nf,
      simp,
      have h12: (7 : zmod 7) = 0 := rfl,
      rw [h12], simp},

    have h13: 7 ∣ (2 * a + (-3) * b),
    { have h50 : (2 * az + (-3) * bz).val = (0 : zmod 7).val := congr_arg zmod.val h11,
      have h51 : (2 * az + (-3) * bz).val = ((2 * az).val + ((-3) * bz).val) % 7 := zmod.val_add _ _,
      have h52 : (2 * az).val = (2 : zmod 7).val * az.val % 7 := zmod.val_mul _ _,
      have h53 : ((-3) * bz).val = (-3 : zmod 7).val * bz.val % 7 := zmod.val_mul _ _,
      rw [h51, h52, h53] at h50,
      simp at h50,
      have h51 : (2 * az.val + 4 * bz.val) % 7 = 0 := h50,
      have h52 : ((2 * (az.val:ℤ) + 4 * (bz.val:ℤ))) % 7 = 0,
      { norm_cast,
        exact h51},
      rw [lemma1 a, lemma1 b] at h52,
      rw [←int.mod_add_div a 7, ←int.mod_add_div b 7],
      have h53 : 2 * (a % 7 + 7 * (a / 7)) + (-3) * (b % 7 + 7 * (b / 7)) =
               2 * (a % 7) + 4 * (b % 7) + 7 * (2 * (a / 7) + (-3) * (b / 7) - (b % 7) ) := by ring,
      rw [h53],
      have h54 : 7 ∣ 7 * (2 * (a / 7) + (-3) * (b / 7) - b % 7) := dvd.intro _ rfl,
      exact dvd_add (int.dvd_of_mod_eq_zero h52) h54 },

    have h14 : (∃ m1, 2 * a + (-3) * b = 7 * m1) := exists_eq_mul_right_of_dvd h13,
    obtain ⟨m1, hm1⟩ := h14,

    have h15: (az + 2 * bz) = 0,
    { rw [hen], ring_nf },
    have h16: 7 ∣ (a + 2 * b),
    { have h50 : (az + 2 * bz).val = (0 : zmod 7).val := congr_arg zmod.val h15,
      have h51 : (az + 2 * bz).val = (az.val + (2 * bz).val) % 7 := zmod.val_add _ _,
      have h53 : (2 * bz).val = (2 : zmod 7).val * bz.val % 7 := zmod.val_mul _ _,
      rw [h51, h53] at h50,
      simp at h50,
      have h51 : (az.val + 2 * bz.val) % 7 = 0 := h50,
      have h52 : (((az.val:ℤ) + 2 * (bz.val:ℤ))) % 7 = 0,
      { norm_cast,
        exact h51},
      rw [lemma1 a, lemma1 b] at h52,
      --rw [int.add_mod] at h52,
      have h52' : 7 ∣ a % 7 + 2 * (b % 7) := int.dvd_of_mod_eq_zero h52,
      rw [←int.mod_add_div a 7, ←int.mod_add_div b 7],
      have h53 : a % 7 + 7 * (a / 7) + 2 * (b % 7 + 7 * (b / 7)) =
              a % 7 + 2 * (b % 7) + 7 * (a / 7 + 2 * (b / 7)) := by ring,
      rw [h53],
      have h54 : 7 ∣ 7 * (a / 7 + 2 * (b / 7)) := dvd.intro _ rfl,
      exact dvd_add h52' h54,
    },

    have h17 : (∃ m2, a + 2 * b = 7 * m2) := exists_eq_mul_right_of_dvd h16,
    obtain ⟨m2, hm2⟩ := h17,

    use m1,
    use m2,

    have h20 : (7 * m1) ^ 2 + 3 * (7 * m2) ^ 2 = 7 * 7 * n,
    { rw [←hm1, ←hm2],
      ring_nf,
      have h18: 7 * (a ^ 2 + 3 * b ^ 2) = 7 * (7 * n) := congr_arg (has_mul.mul 7) hn,
      ring_nf at h18,
      rw [h18]},

    have h21 : (7 * m1) ^ 2 + 3 * (7 * m2) ^ 2 = 7 * 7 * (m1 ^ 2 + 3 * m2 ^ 2) := by ring,

    rw[h21] at h20,
    have h22 : (7:ℤ) * 7 ≠ 0 := by norm_num,
    exact (mul_right_inj' h22).mp h20 },
end


/-
Copyright (c) 2018 Patrick Massot. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Patrick Massot, Chris Hughes, Michael Howes

! This file was ported from Lean 3 source module algebra.group.conj
! leanprover-community/mathlib commit c3291da49cfa65f0d43b094750541c0731edc932
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Algebra.Group.Semiconj
import Mathbin.Algebra.GroupWithZero.Basic
import Mathbin.Algebra.Hom.Aut
import Mathbin.Algebra.Hom.Group

/-!
# Conjugacy of group elements

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

See also `mul_aut.conj` and `quandle.conj`.
-/

universe u v

variable {α : Type u} {β : Type v}

section Monoid

variable [Monoid α] [Monoid β]

#print IsConj /-
/-- We say that `a` is conjugate to `b` if for some unit `c` we have `c * a * c⁻¹ = b`. -/
def IsConj (a b : α) :=
  ∃ c : αˣ, SemiconjBy (↑c) a b
#align is_conj IsConj
-/

#print IsConj.refl /-
@[refl]
theorem IsConj.refl (a : α) : IsConj a a :=
  ⟨1, SemiconjBy.one_left a⟩
#align is_conj.refl IsConj.refl
-/

#print IsConj.symm /-
@[symm]
theorem IsConj.symm {a b : α} : IsConj a b → IsConj b a
  | ⟨c, hc⟩ => ⟨c⁻¹, hc.units_inv_symm_left⟩
#align is_conj.symm IsConj.symm
-/

#print isConj_comm /-
theorem isConj_comm {g h : α} : IsConj g h ↔ IsConj h g :=
  ⟨IsConj.symm, IsConj.symm⟩
#align is_conj_comm isConj_comm
-/

#print IsConj.trans /-
@[trans]
theorem IsConj.trans {a b c : α} : IsConj a b → IsConj b c → IsConj a c
  | ⟨c₁, hc₁⟩, ⟨c₂, hc₂⟩ => ⟨c₂ * c₁, hc₂.mul_left hc₁⟩
#align is_conj.trans IsConj.trans
-/

#print isConj_iff_eq /-
@[simp]
theorem isConj_iff_eq {α : Type _} [CommMonoid α] {a b : α} : IsConj a b ↔ a = b :=
  ⟨fun ⟨c, hc⟩ =>
    by
    rw [SemiconjBy, mul_comm, ← Units.mul_inv_eq_iff_eq_mul, mul_assoc, c.mul_inv, mul_one] at hc
    exact hc, fun h => by rw [h]⟩
#align is_conj_iff_eq isConj_iff_eq
-/

/- warning: monoid_hom.map_is_conj -> MonoidHom.map_isConj is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} [_inst_1 : Monoid.{u1} α] [_inst_2 : Monoid.{u2} β] (f : MonoidHom.{u1, u2} α β (Monoid.toMulOneClass.{u1} α _inst_1) (Monoid.toMulOneClass.{u2} β _inst_2)) {a : α} {b : α}, (IsConj.{u1} α _inst_1 a b) -> (IsConj.{u2} β _inst_2 (coeFn.{max (succ u2) (succ u1), max (succ u1) (succ u2)} (MonoidHom.{u1, u2} α β (Monoid.toMulOneClass.{u1} α _inst_1) (Monoid.toMulOneClass.{u2} β _inst_2)) (fun (_x : MonoidHom.{u1, u2} α β (Monoid.toMulOneClass.{u1} α _inst_1) (Monoid.toMulOneClass.{u2} β _inst_2)) => α -> β) (MonoidHom.hasCoeToFun.{u1, u2} α β (Monoid.toMulOneClass.{u1} α _inst_1) (Monoid.toMulOneClass.{u2} β _inst_2)) f a) (coeFn.{max (succ u2) (succ u1), max (succ u1) (succ u2)} (MonoidHom.{u1, u2} α β (Monoid.toMulOneClass.{u1} α _inst_1) (Monoid.toMulOneClass.{u2} β _inst_2)) (fun (_x : MonoidHom.{u1, u2} α β (Monoid.toMulOneClass.{u1} α _inst_1) (Monoid.toMulOneClass.{u2} β _inst_2)) => α -> β) (MonoidHom.hasCoeToFun.{u1, u2} α β (Monoid.toMulOneClass.{u1} α _inst_1) (Monoid.toMulOneClass.{u2} β _inst_2)) f b))
but is expected to have type
  forall {α : Type.{u1}} {β : Type.{u2}} [_inst_1 : Monoid.{u1} α] [_inst_2 : Monoid.{u2} β] (f : MonoidHom.{u1, u2} α β (Monoid.toMulOneClass.{u1} α _inst_1) (Monoid.toMulOneClass.{u2} β _inst_2)) {a : α} {b : α}, (IsConj.{u1} α _inst_1 a b) -> (IsConj.{u2} ((fun (x._@.Mathlib.Algebra.Hom.Group._hyg.2391 : α) => β) a) _inst_2 (FunLike.coe.{max (succ u1) (succ u2), succ u1, succ u2} (MonoidHom.{u1, u2} α β (Monoid.toMulOneClass.{u1} α _inst_1) (Monoid.toMulOneClass.{u2} β _inst_2)) α (fun (_x : α) => (fun (x._@.Mathlib.Algebra.Hom.Group._hyg.2391 : α) => β) _x) (MulHomClass.toFunLike.{max u1 u2, u1, u2} (MonoidHom.{u1, u2} α β (Monoid.toMulOneClass.{u1} α _inst_1) (Monoid.toMulOneClass.{u2} β _inst_2)) α β (MulOneClass.toMul.{u1} α (Monoid.toMulOneClass.{u1} α _inst_1)) (MulOneClass.toMul.{u2} β (Monoid.toMulOneClass.{u2} β _inst_2)) (MonoidHomClass.toMulHomClass.{max u1 u2, u1, u2} (MonoidHom.{u1, u2} α β (Monoid.toMulOneClass.{u1} α _inst_1) (Monoid.toMulOneClass.{u2} β _inst_2)) α β (Monoid.toMulOneClass.{u1} α _inst_1) (Monoid.toMulOneClass.{u2} β _inst_2) (MonoidHom.monoidHomClass.{u1, u2} α β (Monoid.toMulOneClass.{u1} α _inst_1) (Monoid.toMulOneClass.{u2} β _inst_2)))) f a) (FunLike.coe.{max (succ u1) (succ u2), succ u1, succ u2} (MonoidHom.{u1, u2} α β (Monoid.toMulOneClass.{u1} α _inst_1) (Monoid.toMulOneClass.{u2} β _inst_2)) α (fun (_x : α) => (fun (x._@.Mathlib.Algebra.Hom.Group._hyg.2391 : α) => β) _x) (MulHomClass.toFunLike.{max u1 u2, u1, u2} (MonoidHom.{u1, u2} α β (Monoid.toMulOneClass.{u1} α _inst_1) (Monoid.toMulOneClass.{u2} β _inst_2)) α β (MulOneClass.toMul.{u1} α (Monoid.toMulOneClass.{u1} α _inst_1)) (MulOneClass.toMul.{u2} β (Monoid.toMulOneClass.{u2} β _inst_2)) (MonoidHomClass.toMulHomClass.{max u1 u2, u1, u2} (MonoidHom.{u1, u2} α β (Monoid.toMulOneClass.{u1} α _inst_1) (Monoid.toMulOneClass.{u2} β _inst_2)) α β (Monoid.toMulOneClass.{u1} α _inst_1) (Monoid.toMulOneClass.{u2} β _inst_2) (MonoidHom.monoidHomClass.{u1, u2} α β (Monoid.toMulOneClass.{u1} α _inst_1) (Monoid.toMulOneClass.{u2} β _inst_2)))) f b))
Case conversion may be inaccurate. Consider using '#align monoid_hom.map_is_conj MonoidHom.map_isConjₓ'. -/
protected theorem MonoidHom.map_isConj (f : α →* β) {a b : α} : IsConj a b → IsConj (f a) (f b)
  | ⟨c, hc⟩ => ⟨Units.map f c, by rw [Units.coe_map, SemiconjBy, ← f.map_mul, hc.eq, f.map_mul]⟩
#align monoid_hom.map_is_conj MonoidHom.map_isConj

end Monoid

section CancelMonoid

variable [CancelMonoid α]

/- warning: is_conj_one_right -> isConj_one_right is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} [_inst_1 : CancelMonoid.{u1} α] {a : α}, Iff (IsConj.{u1} α (RightCancelMonoid.toMonoid.{u1} α (CancelMonoid.toRightCancelMonoid.{u1} α _inst_1)) (OfNat.ofNat.{u1} α 1 (OfNat.mk.{u1} α 1 (One.one.{u1} α (MulOneClass.toHasOne.{u1} α (Monoid.toMulOneClass.{u1} α (RightCancelMonoid.toMonoid.{u1} α (CancelMonoid.toRightCancelMonoid.{u1} α _inst_1))))))) a) (Eq.{succ u1} α a (OfNat.ofNat.{u1} α 1 (OfNat.mk.{u1} α 1 (One.one.{u1} α (MulOneClass.toHasOne.{u1} α (Monoid.toMulOneClass.{u1} α (RightCancelMonoid.toMonoid.{u1} α (CancelMonoid.toRightCancelMonoid.{u1} α _inst_1))))))))
but is expected to have type
  forall {α : Type.{u1}} [_inst_1 : CancelMonoid.{u1} α] {a : α}, Iff (IsConj.{u1} α (RightCancelMonoid.toMonoid.{u1} α (CancelMonoid.toRightCancelMonoid.{u1} α _inst_1)) (OfNat.ofNat.{u1} α 1 (One.toOfNat1.{u1} α (RightCancelMonoid.toOne.{u1} α (CancelMonoid.toRightCancelMonoid.{u1} α _inst_1)))) a) (Eq.{succ u1} α a (OfNat.ofNat.{u1} α 1 (One.toOfNat1.{u1} α (RightCancelMonoid.toOne.{u1} α (CancelMonoid.toRightCancelMonoid.{u1} α _inst_1)))))
Case conversion may be inaccurate. Consider using '#align is_conj_one_right isConj_one_rightₓ'. -/
-- These lemmas hold for `right_cancel_monoid` with the current proofs, but for the sake of
-- not duplicating code (these lemmas also hold for `left_cancel_monoids`) we leave these
-- not generalised.
@[simp]
theorem isConj_one_right {a : α} : IsConj 1 a ↔ a = 1 :=
  ⟨fun ⟨c, hc⟩ => mul_right_cancel (hc.symm.trans ((mul_one _).trans (one_mul _).symm)), fun h => by
    rw [h]⟩
#align is_conj_one_right isConj_one_right

/- warning: is_conj_one_left -> isConj_one_left is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} [_inst_1 : CancelMonoid.{u1} α] {a : α}, Iff (IsConj.{u1} α (RightCancelMonoid.toMonoid.{u1} α (CancelMonoid.toRightCancelMonoid.{u1} α _inst_1)) a (OfNat.ofNat.{u1} α 1 (OfNat.mk.{u1} α 1 (One.one.{u1} α (MulOneClass.toHasOne.{u1} α (Monoid.toMulOneClass.{u1} α (RightCancelMonoid.toMonoid.{u1} α (CancelMonoid.toRightCancelMonoid.{u1} α _inst_1)))))))) (Eq.{succ u1} α a (OfNat.ofNat.{u1} α 1 (OfNat.mk.{u1} α 1 (One.one.{u1} α (MulOneClass.toHasOne.{u1} α (Monoid.toMulOneClass.{u1} α (RightCancelMonoid.toMonoid.{u1} α (CancelMonoid.toRightCancelMonoid.{u1} α _inst_1))))))))
but is expected to have type
  forall {α : Type.{u1}} [_inst_1 : CancelMonoid.{u1} α] {a : α}, Iff (IsConj.{u1} α (RightCancelMonoid.toMonoid.{u1} α (CancelMonoid.toRightCancelMonoid.{u1} α _inst_1)) a (OfNat.ofNat.{u1} α 1 (One.toOfNat1.{u1} α (RightCancelMonoid.toOne.{u1} α (CancelMonoid.toRightCancelMonoid.{u1} α _inst_1))))) (Eq.{succ u1} α a (OfNat.ofNat.{u1} α 1 (One.toOfNat1.{u1} α (RightCancelMonoid.toOne.{u1} α (CancelMonoid.toRightCancelMonoid.{u1} α _inst_1)))))
Case conversion may be inaccurate. Consider using '#align is_conj_one_left isConj_one_leftₓ'. -/
@[simp]
theorem isConj_one_left {a : α} : IsConj a 1 ↔ a = 1 :=
  calc
    IsConj a 1 ↔ IsConj 1 a := ⟨IsConj.symm, IsConj.symm⟩
    _ ↔ a = 1 := isConj_one_right

#align is_conj_one_left isConj_one_left

end CancelMonoid

section Group

variable [Group α]

/- warning: is_conj_iff -> isConj_iff is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} [_inst_1 : Group.{u1} α] {a : α} {b : α}, Iff (IsConj.{u1} α (DivInvMonoid.toMonoid.{u1} α (Group.toDivInvMonoid.{u1} α _inst_1)) a b) (Exists.{succ u1} α (fun (c : α) => Eq.{succ u1} α (HMul.hMul.{u1, u1, u1} α α α (instHMul.{u1} α (MulOneClass.toHasMul.{u1} α (Monoid.toMulOneClass.{u1} α (DivInvMonoid.toMonoid.{u1} α (Group.toDivInvMonoid.{u1} α _inst_1))))) (HMul.hMul.{u1, u1, u1} α α α (instHMul.{u1} α (MulOneClass.toHasMul.{u1} α (Monoid.toMulOneClass.{u1} α (DivInvMonoid.toMonoid.{u1} α (Group.toDivInvMonoid.{u1} α _inst_1))))) c a) (Inv.inv.{u1} α (DivInvMonoid.toHasInv.{u1} α (Group.toDivInvMonoid.{u1} α _inst_1)) c)) b))
but is expected to have type
  forall {α : Type.{u1}} [_inst_1 : Group.{u1} α] {a : α} {b : α}, Iff (IsConj.{u1} α (DivInvMonoid.toMonoid.{u1} α (Group.toDivInvMonoid.{u1} α _inst_1)) a b) (Exists.{succ u1} α (fun (c : α) => Eq.{succ u1} α (HMul.hMul.{u1, u1, u1} α α α (instHMul.{u1} α (MulOneClass.toMul.{u1} α (Monoid.toMulOneClass.{u1} α (DivInvMonoid.toMonoid.{u1} α (Group.toDivInvMonoid.{u1} α _inst_1))))) (HMul.hMul.{u1, u1, u1} α α α (instHMul.{u1} α (MulOneClass.toMul.{u1} α (Monoid.toMulOneClass.{u1} α (DivInvMonoid.toMonoid.{u1} α (Group.toDivInvMonoid.{u1} α _inst_1))))) c a) (Inv.inv.{u1} α (InvOneClass.toInv.{u1} α (DivInvOneMonoid.toInvOneClass.{u1} α (DivisionMonoid.toDivInvOneMonoid.{u1} α (Group.toDivisionMonoid.{u1} α _inst_1)))) c)) b))
Case conversion may be inaccurate. Consider using '#align is_conj_iff isConj_iffₓ'. -/
@[simp]
theorem isConj_iff {a b : α} : IsConj a b ↔ ∃ c : α, c * a * c⁻¹ = b :=
  ⟨fun ⟨c, hc⟩ => ⟨c, mul_inv_eq_iff_eq_mul.2 hc⟩, fun ⟨c, hc⟩ =>
    ⟨⟨c, c⁻¹, mul_inv_self c, inv_mul_self c⟩, mul_inv_eq_iff_eq_mul.1 hc⟩⟩
#align is_conj_iff isConj_iff

/- warning: conj_inv -> conj_inv is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} [_inst_1 : Group.{u1} α] {a : α} {b : α}, Eq.{succ u1} α (Inv.inv.{u1} α (DivInvMonoid.toHasInv.{u1} α (Group.toDivInvMonoid.{u1} α _inst_1)) (HMul.hMul.{u1, u1, u1} α α α (instHMul.{u1} α (MulOneClass.toHasMul.{u1} α (Monoid.toMulOneClass.{u1} α (DivInvMonoid.toMonoid.{u1} α (Group.toDivInvMonoid.{u1} α _inst_1))))) (HMul.hMul.{u1, u1, u1} α α α (instHMul.{u1} α (MulOneClass.toHasMul.{u1} α (Monoid.toMulOneClass.{u1} α (DivInvMonoid.toMonoid.{u1} α (Group.toDivInvMonoid.{u1} α _inst_1))))) b a) (Inv.inv.{u1} α (DivInvMonoid.toHasInv.{u1} α (Group.toDivInvMonoid.{u1} α _inst_1)) b))) (HMul.hMul.{u1, u1, u1} α α α (instHMul.{u1} α (MulOneClass.toHasMul.{u1} α (Monoid.toMulOneClass.{u1} α (DivInvMonoid.toMonoid.{u1} α (Group.toDivInvMonoid.{u1} α _inst_1))))) (HMul.hMul.{u1, u1, u1} α α α (instHMul.{u1} α (MulOneClass.toHasMul.{u1} α (Monoid.toMulOneClass.{u1} α (DivInvMonoid.toMonoid.{u1} α (Group.toDivInvMonoid.{u1} α _inst_1))))) b (Inv.inv.{u1} α (DivInvMonoid.toHasInv.{u1} α (Group.toDivInvMonoid.{u1} α _inst_1)) a)) (Inv.inv.{u1} α (DivInvMonoid.toHasInv.{u1} α (Group.toDivInvMonoid.{u1} α _inst_1)) b))
but is expected to have type
  forall {α : Type.{u1}} [_inst_1 : Group.{u1} α] {a : α} {b : α}, Eq.{succ u1} α (Inv.inv.{u1} α (InvOneClass.toInv.{u1} α (DivInvOneMonoid.toInvOneClass.{u1} α (DivisionMonoid.toDivInvOneMonoid.{u1} α (Group.toDivisionMonoid.{u1} α _inst_1)))) (HMul.hMul.{u1, u1, u1} α α α (instHMul.{u1} α (MulOneClass.toMul.{u1} α (Monoid.toMulOneClass.{u1} α (DivInvMonoid.toMonoid.{u1} α (Group.toDivInvMonoid.{u1} α _inst_1))))) (HMul.hMul.{u1, u1, u1} α α α (instHMul.{u1} α (MulOneClass.toMul.{u1} α (Monoid.toMulOneClass.{u1} α (DivInvMonoid.toMonoid.{u1} α (Group.toDivInvMonoid.{u1} α _inst_1))))) b a) (Inv.inv.{u1} α (InvOneClass.toInv.{u1} α (DivInvOneMonoid.toInvOneClass.{u1} α (DivisionMonoid.toDivInvOneMonoid.{u1} α (Group.toDivisionMonoid.{u1} α _inst_1)))) b))) (HMul.hMul.{u1, u1, u1} α α α (instHMul.{u1} α (MulOneClass.toMul.{u1} α (Monoid.toMulOneClass.{u1} α (DivInvMonoid.toMonoid.{u1} α (Group.toDivInvMonoid.{u1} α _inst_1))))) (HMul.hMul.{u1, u1, u1} α α α (instHMul.{u1} α (MulOneClass.toMul.{u1} α (Monoid.toMulOneClass.{u1} α (DivInvMonoid.toMonoid.{u1} α (Group.toDivInvMonoid.{u1} α _inst_1))))) b (Inv.inv.{u1} α (InvOneClass.toInv.{u1} α (DivInvOneMonoid.toInvOneClass.{u1} α (DivisionMonoid.toDivInvOneMonoid.{u1} α (Group.toDivisionMonoid.{u1} α _inst_1)))) a)) (Inv.inv.{u1} α (InvOneClass.toInv.{u1} α (DivInvOneMonoid.toInvOneClass.{u1} α (DivisionMonoid.toDivInvOneMonoid.{u1} α (Group.toDivisionMonoid.{u1} α _inst_1)))) b))
Case conversion may be inaccurate. Consider using '#align conj_inv conj_invₓ'. -/
@[simp]
theorem conj_inv {a b : α} : (b * a * b⁻¹)⁻¹ = b * a⁻¹ * b⁻¹ :=
  ((MulAut.conj b).map_inv a).symm
#align conj_inv conj_inv

/- warning: conj_mul -> conj_mul is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} [_inst_1 : Group.{u1} α] {a : α} {b : α} {c : α}, Eq.{succ u1} α (HMul.hMul.{u1, u1, u1} α α α (instHMul.{u1} α (MulOneClass.toHasMul.{u1} α (Monoid.toMulOneClass.{u1} α (DivInvMonoid.toMonoid.{u1} α (Group.toDivInvMonoid.{u1} α _inst_1))))) (HMul.hMul.{u1, u1, u1} α α α (instHMul.{u1} α (MulOneClass.toHasMul.{u1} α (Monoid.toMulOneClass.{u1} α (DivInvMonoid.toMonoid.{u1} α (Group.toDivInvMonoid.{u1} α _inst_1))))) (HMul.hMul.{u1, u1, u1} α α α (instHMul.{u1} α (MulOneClass.toHasMul.{u1} α (Monoid.toMulOneClass.{u1} α (DivInvMonoid.toMonoid.{u1} α (Group.toDivInvMonoid.{u1} α _inst_1))))) b a) (Inv.inv.{u1} α (DivInvMonoid.toHasInv.{u1} α (Group.toDivInvMonoid.{u1} α _inst_1)) b)) (HMul.hMul.{u1, u1, u1} α α α (instHMul.{u1} α (MulOneClass.toHasMul.{u1} α (Monoid.toMulOneClass.{u1} α (DivInvMonoid.toMonoid.{u1} α (Group.toDivInvMonoid.{u1} α _inst_1))))) (HMul.hMul.{u1, u1, u1} α α α (instHMul.{u1} α (MulOneClass.toHasMul.{u1} α (Monoid.toMulOneClass.{u1} α (DivInvMonoid.toMonoid.{u1} α (Group.toDivInvMonoid.{u1} α _inst_1))))) b c) (Inv.inv.{u1} α (DivInvMonoid.toHasInv.{u1} α (Group.toDivInvMonoid.{u1} α _inst_1)) b))) (HMul.hMul.{u1, u1, u1} α α α (instHMul.{u1} α (MulOneClass.toHasMul.{u1} α (Monoid.toMulOneClass.{u1} α (DivInvMonoid.toMonoid.{u1} α (Group.toDivInvMonoid.{u1} α _inst_1))))) (HMul.hMul.{u1, u1, u1} α α α (instHMul.{u1} α (MulOneClass.toHasMul.{u1} α (Monoid.toMulOneClass.{u1} α (DivInvMonoid.toMonoid.{u1} α (Group.toDivInvMonoid.{u1} α _inst_1))))) b (HMul.hMul.{u1, u1, u1} α α α (instHMul.{u1} α (MulOneClass.toHasMul.{u1} α (Monoid.toMulOneClass.{u1} α (DivInvMonoid.toMonoid.{u1} α (Group.toDivInvMonoid.{u1} α _inst_1))))) a c)) (Inv.inv.{u1} α (DivInvMonoid.toHasInv.{u1} α (Group.toDivInvMonoid.{u1} α _inst_1)) b))
but is expected to have type
  forall {α : Type.{u1}} [_inst_1 : Group.{u1} α] {a : α} {b : α} {c : α}, Eq.{succ u1} α (HMul.hMul.{u1, u1, u1} α α α (instHMul.{u1} α (MulOneClass.toMul.{u1} α (Monoid.toMulOneClass.{u1} α (DivInvMonoid.toMonoid.{u1} α (Group.toDivInvMonoid.{u1} α _inst_1))))) (HMul.hMul.{u1, u1, u1} α α α (instHMul.{u1} α (MulOneClass.toMul.{u1} α (Monoid.toMulOneClass.{u1} α (DivInvMonoid.toMonoid.{u1} α (Group.toDivInvMonoid.{u1} α _inst_1))))) (HMul.hMul.{u1, u1, u1} α α α (instHMul.{u1} α (MulOneClass.toMul.{u1} α (Monoid.toMulOneClass.{u1} α (DivInvMonoid.toMonoid.{u1} α (Group.toDivInvMonoid.{u1} α _inst_1))))) b a) (Inv.inv.{u1} α (InvOneClass.toInv.{u1} α (DivInvOneMonoid.toInvOneClass.{u1} α (DivisionMonoid.toDivInvOneMonoid.{u1} α (Group.toDivisionMonoid.{u1} α _inst_1)))) b)) (HMul.hMul.{u1, u1, u1} α α α (instHMul.{u1} α (MulOneClass.toMul.{u1} α (Monoid.toMulOneClass.{u1} α (DivInvMonoid.toMonoid.{u1} α (Group.toDivInvMonoid.{u1} α _inst_1))))) (HMul.hMul.{u1, u1, u1} α α α (instHMul.{u1} α (MulOneClass.toMul.{u1} α (Monoid.toMulOneClass.{u1} α (DivInvMonoid.toMonoid.{u1} α (Group.toDivInvMonoid.{u1} α _inst_1))))) b c) (Inv.inv.{u1} α (InvOneClass.toInv.{u1} α (DivInvOneMonoid.toInvOneClass.{u1} α (DivisionMonoid.toDivInvOneMonoid.{u1} α (Group.toDivisionMonoid.{u1} α _inst_1)))) b))) (HMul.hMul.{u1, u1, u1} α α α (instHMul.{u1} α (MulOneClass.toMul.{u1} α (Monoid.toMulOneClass.{u1} α (DivInvMonoid.toMonoid.{u1} α (Group.toDivInvMonoid.{u1} α _inst_1))))) (HMul.hMul.{u1, u1, u1} α α α (instHMul.{u1} α (MulOneClass.toMul.{u1} α (Monoid.toMulOneClass.{u1} α (DivInvMonoid.toMonoid.{u1} α (Group.toDivInvMonoid.{u1} α _inst_1))))) b (HMul.hMul.{u1, u1, u1} α α α (instHMul.{u1} α (MulOneClass.toMul.{u1} α (Monoid.toMulOneClass.{u1} α (DivInvMonoid.toMonoid.{u1} α (Group.toDivInvMonoid.{u1} α _inst_1))))) a c)) (Inv.inv.{u1} α (InvOneClass.toInv.{u1} α (DivInvOneMonoid.toInvOneClass.{u1} α (DivisionMonoid.toDivInvOneMonoid.{u1} α (Group.toDivisionMonoid.{u1} α _inst_1)))) b))
Case conversion may be inaccurate. Consider using '#align conj_mul conj_mulₓ'. -/
@[simp]
theorem conj_mul {a b c : α} : b * a * b⁻¹ * (b * c * b⁻¹) = b * (a * c) * b⁻¹ :=
  ((MulAut.conj b).map_mul a c).symm
#align conj_mul conj_mul

/- warning: conj_pow -> conj_pow is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} [_inst_1 : Group.{u1} α] {i : Nat} {a : α} {b : α}, Eq.{succ u1} α (HPow.hPow.{u1, 0, u1} α Nat α (instHPow.{u1, 0} α Nat (Monoid.Pow.{u1} α (DivInvMonoid.toMonoid.{u1} α (Group.toDivInvMonoid.{u1} α _inst_1)))) (HMul.hMul.{u1, u1, u1} α α α (instHMul.{u1} α (MulOneClass.toHasMul.{u1} α (Monoid.toMulOneClass.{u1} α (DivInvMonoid.toMonoid.{u1} α (Group.toDivInvMonoid.{u1} α _inst_1))))) (HMul.hMul.{u1, u1, u1} α α α (instHMul.{u1} α (MulOneClass.toHasMul.{u1} α (Monoid.toMulOneClass.{u1} α (DivInvMonoid.toMonoid.{u1} α (Group.toDivInvMonoid.{u1} α _inst_1))))) a b) (Inv.inv.{u1} α (DivInvMonoid.toHasInv.{u1} α (Group.toDivInvMonoid.{u1} α _inst_1)) a)) i) (HMul.hMul.{u1, u1, u1} α α α (instHMul.{u1} α (MulOneClass.toHasMul.{u1} α (Monoid.toMulOneClass.{u1} α (DivInvMonoid.toMonoid.{u1} α (Group.toDivInvMonoid.{u1} α _inst_1))))) (HMul.hMul.{u1, u1, u1} α α α (instHMul.{u1} α (MulOneClass.toHasMul.{u1} α (Monoid.toMulOneClass.{u1} α (DivInvMonoid.toMonoid.{u1} α (Group.toDivInvMonoid.{u1} α _inst_1))))) a (HPow.hPow.{u1, 0, u1} α Nat α (instHPow.{u1, 0} α Nat (Monoid.Pow.{u1} α (DivInvMonoid.toMonoid.{u1} α (Group.toDivInvMonoid.{u1} α _inst_1)))) b i)) (Inv.inv.{u1} α (DivInvMonoid.toHasInv.{u1} α (Group.toDivInvMonoid.{u1} α _inst_1)) a))
but is expected to have type
  forall {α : Type.{u1}} [_inst_1 : Group.{u1} α] {i : Nat} {a : α} {b : α}, Eq.{succ u1} α (HPow.hPow.{u1, 0, u1} α Nat α (instHPow.{u1, 0} α Nat (Monoid.Pow.{u1} α (DivInvMonoid.toMonoid.{u1} α (Group.toDivInvMonoid.{u1} α _inst_1)))) (HMul.hMul.{u1, u1, u1} α α α (instHMul.{u1} α (MulOneClass.toMul.{u1} α (Monoid.toMulOneClass.{u1} α (DivInvMonoid.toMonoid.{u1} α (Group.toDivInvMonoid.{u1} α _inst_1))))) (HMul.hMul.{u1, u1, u1} α α α (instHMul.{u1} α (MulOneClass.toMul.{u1} α (Monoid.toMulOneClass.{u1} α (DivInvMonoid.toMonoid.{u1} α (Group.toDivInvMonoid.{u1} α _inst_1))))) a b) (Inv.inv.{u1} α (InvOneClass.toInv.{u1} α (DivInvOneMonoid.toInvOneClass.{u1} α (DivisionMonoid.toDivInvOneMonoid.{u1} α (Group.toDivisionMonoid.{u1} α _inst_1)))) a)) i) (HMul.hMul.{u1, u1, u1} α α α (instHMul.{u1} α (MulOneClass.toMul.{u1} α (Monoid.toMulOneClass.{u1} α (DivInvMonoid.toMonoid.{u1} α (Group.toDivInvMonoid.{u1} α _inst_1))))) (HMul.hMul.{u1, u1, u1} α α α (instHMul.{u1} α (MulOneClass.toMul.{u1} α (Monoid.toMulOneClass.{u1} α (DivInvMonoid.toMonoid.{u1} α (Group.toDivInvMonoid.{u1} α _inst_1))))) a (HPow.hPow.{u1, 0, u1} α Nat α (instHPow.{u1, 0} α Nat (Monoid.Pow.{u1} α (DivInvMonoid.toMonoid.{u1} α (Group.toDivInvMonoid.{u1} α _inst_1)))) b i)) (Inv.inv.{u1} α (InvOneClass.toInv.{u1} α (DivInvOneMonoid.toInvOneClass.{u1} α (DivisionMonoid.toDivInvOneMonoid.{u1} α (Group.toDivisionMonoid.{u1} α _inst_1)))) a))
Case conversion may be inaccurate. Consider using '#align conj_pow conj_powₓ'. -/
@[simp]
theorem conj_pow {i : ℕ} {a b : α} : (a * b * a⁻¹) ^ i = a * b ^ i * a⁻¹ :=
  by
  induction' i with i hi
  · simp
  · simp [pow_succ, hi]
#align conj_pow conj_pow

/- warning: conj_zpow -> conj_zpow is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} [_inst_1 : Group.{u1} α] {i : Int} {a : α} {b : α}, Eq.{succ u1} α (HPow.hPow.{u1, 0, u1} α Int α (instHPow.{u1, 0} α Int (DivInvMonoid.Pow.{u1} α (Group.toDivInvMonoid.{u1} α _inst_1))) (HMul.hMul.{u1, u1, u1} α α α (instHMul.{u1} α (MulOneClass.toHasMul.{u1} α (Monoid.toMulOneClass.{u1} α (DivInvMonoid.toMonoid.{u1} α (Group.toDivInvMonoid.{u1} α _inst_1))))) (HMul.hMul.{u1, u1, u1} α α α (instHMul.{u1} α (MulOneClass.toHasMul.{u1} α (Monoid.toMulOneClass.{u1} α (DivInvMonoid.toMonoid.{u1} α (Group.toDivInvMonoid.{u1} α _inst_1))))) a b) (Inv.inv.{u1} α (DivInvMonoid.toHasInv.{u1} α (Group.toDivInvMonoid.{u1} α _inst_1)) a)) i) (HMul.hMul.{u1, u1, u1} α α α (instHMul.{u1} α (MulOneClass.toHasMul.{u1} α (Monoid.toMulOneClass.{u1} α (DivInvMonoid.toMonoid.{u1} α (Group.toDivInvMonoid.{u1} α _inst_1))))) (HMul.hMul.{u1, u1, u1} α α α (instHMul.{u1} α (MulOneClass.toHasMul.{u1} α (Monoid.toMulOneClass.{u1} α (DivInvMonoid.toMonoid.{u1} α (Group.toDivInvMonoid.{u1} α _inst_1))))) a (HPow.hPow.{u1, 0, u1} α Int α (instHPow.{u1, 0} α Int (DivInvMonoid.Pow.{u1} α (Group.toDivInvMonoid.{u1} α _inst_1))) b i)) (Inv.inv.{u1} α (DivInvMonoid.toHasInv.{u1} α (Group.toDivInvMonoid.{u1} α _inst_1)) a))
but is expected to have type
  forall {α : Type.{u1}} [_inst_1 : Group.{u1} α] {i : Int} {a : α} {b : α}, Eq.{succ u1} α (HPow.hPow.{u1, 0, u1} α Int α (instHPow.{u1, 0} α Int (DivInvMonoid.Pow.{u1} α (Group.toDivInvMonoid.{u1} α _inst_1))) (HMul.hMul.{u1, u1, u1} α α α (instHMul.{u1} α (MulOneClass.toMul.{u1} α (Monoid.toMulOneClass.{u1} α (DivInvMonoid.toMonoid.{u1} α (Group.toDivInvMonoid.{u1} α _inst_1))))) (HMul.hMul.{u1, u1, u1} α α α (instHMul.{u1} α (MulOneClass.toMul.{u1} α (Monoid.toMulOneClass.{u1} α (DivInvMonoid.toMonoid.{u1} α (Group.toDivInvMonoid.{u1} α _inst_1))))) a b) (Inv.inv.{u1} α (InvOneClass.toInv.{u1} α (DivInvOneMonoid.toInvOneClass.{u1} α (DivisionMonoid.toDivInvOneMonoid.{u1} α (Group.toDivisionMonoid.{u1} α _inst_1)))) a)) i) (HMul.hMul.{u1, u1, u1} α α α (instHMul.{u1} α (MulOneClass.toMul.{u1} α (Monoid.toMulOneClass.{u1} α (DivInvMonoid.toMonoid.{u1} α (Group.toDivInvMonoid.{u1} α _inst_1))))) (HMul.hMul.{u1, u1, u1} α α α (instHMul.{u1} α (MulOneClass.toMul.{u1} α (Monoid.toMulOneClass.{u1} α (DivInvMonoid.toMonoid.{u1} α (Group.toDivInvMonoid.{u1} α _inst_1))))) a (HPow.hPow.{u1, 0, u1} α Int α (instHPow.{u1, 0} α Int (DivInvMonoid.Pow.{u1} α (Group.toDivInvMonoid.{u1} α _inst_1))) b i)) (Inv.inv.{u1} α (InvOneClass.toInv.{u1} α (DivInvOneMonoid.toInvOneClass.{u1} α (DivisionMonoid.toDivInvOneMonoid.{u1} α (Group.toDivisionMonoid.{u1} α _inst_1)))) a))
Case conversion may be inaccurate. Consider using '#align conj_zpow conj_zpowₓ'. -/
@[simp]
theorem conj_zpow {i : ℤ} {a b : α} : (a * b * a⁻¹) ^ i = a * b ^ i * a⁻¹ :=
  by
  induction i
  · simp
  · simp [zpow_negSucc, conj_pow]
#align conj_zpow conj_zpow

/- warning: conj_injective -> conj_injective is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} [_inst_1 : Group.{u1} α] {x : α}, Function.Injective.{succ u1, succ u1} α α (fun (g : α) => HMul.hMul.{u1, u1, u1} α α α (instHMul.{u1} α (MulOneClass.toHasMul.{u1} α (Monoid.toMulOneClass.{u1} α (DivInvMonoid.toMonoid.{u1} α (Group.toDivInvMonoid.{u1} α _inst_1))))) (HMul.hMul.{u1, u1, u1} α α α (instHMul.{u1} α (MulOneClass.toHasMul.{u1} α (Monoid.toMulOneClass.{u1} α (DivInvMonoid.toMonoid.{u1} α (Group.toDivInvMonoid.{u1} α _inst_1))))) x g) (Inv.inv.{u1} α (DivInvMonoid.toHasInv.{u1} α (Group.toDivInvMonoid.{u1} α _inst_1)) x))
but is expected to have type
  forall {α : Type.{u1}} [_inst_1 : Group.{u1} α] {x : α}, Function.Injective.{succ u1, succ u1} α α (fun (g : α) => HMul.hMul.{u1, u1, u1} α α α (instHMul.{u1} α (MulOneClass.toMul.{u1} α (Monoid.toMulOneClass.{u1} α (DivInvMonoid.toMonoid.{u1} α (Group.toDivInvMonoid.{u1} α _inst_1))))) (HMul.hMul.{u1, u1, u1} α α α (instHMul.{u1} α (MulOneClass.toMul.{u1} α (Monoid.toMulOneClass.{u1} α (DivInvMonoid.toMonoid.{u1} α (Group.toDivInvMonoid.{u1} α _inst_1))))) x g) (Inv.inv.{u1} α (InvOneClass.toInv.{u1} α (DivInvOneMonoid.toInvOneClass.{u1} α (DivisionMonoid.toDivInvOneMonoid.{u1} α (Group.toDivisionMonoid.{u1} α _inst_1)))) x))
Case conversion may be inaccurate. Consider using '#align conj_injective conj_injectiveₓ'. -/
theorem conj_injective {x : α} : Function.Injective fun g : α => x * g * x⁻¹ :=
  (MulAut.conj x).Injective
#align conj_injective conj_injective

end Group

/- warning: is_conj_iff₀ -> isConj_iff₀ is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} [_inst_1 : GroupWithZero.{u1} α] {a : α} {b : α}, Iff (IsConj.{u1} α (MonoidWithZero.toMonoid.{u1} α (GroupWithZero.toMonoidWithZero.{u1} α _inst_1)) a b) (Exists.{succ u1} α (fun (c : α) => And (Ne.{succ u1} α c (OfNat.ofNat.{u1} α 0 (OfNat.mk.{u1} α 0 (Zero.zero.{u1} α (MulZeroClass.toHasZero.{u1} α (MulZeroOneClass.toMulZeroClass.{u1} α (MonoidWithZero.toMulZeroOneClass.{u1} α (GroupWithZero.toMonoidWithZero.{u1} α _inst_1)))))))) (Eq.{succ u1} α (HMul.hMul.{u1, u1, u1} α α α (instHMul.{u1} α (MulZeroClass.toHasMul.{u1} α (MulZeroOneClass.toMulZeroClass.{u1} α (MonoidWithZero.toMulZeroOneClass.{u1} α (GroupWithZero.toMonoidWithZero.{u1} α _inst_1))))) (HMul.hMul.{u1, u1, u1} α α α (instHMul.{u1} α (MulZeroClass.toHasMul.{u1} α (MulZeroOneClass.toMulZeroClass.{u1} α (MonoidWithZero.toMulZeroOneClass.{u1} α (GroupWithZero.toMonoidWithZero.{u1} α _inst_1))))) c a) (Inv.inv.{u1} α (DivInvMonoid.toHasInv.{u1} α (GroupWithZero.toDivInvMonoid.{u1} α _inst_1)) c)) b)))
but is expected to have type
  forall {α : Type.{u1}} [_inst_1 : GroupWithZero.{u1} α] {a : α} {b : α}, Iff (IsConj.{u1} α (MonoidWithZero.toMonoid.{u1} α (GroupWithZero.toMonoidWithZero.{u1} α _inst_1)) a b) (Exists.{succ u1} α (fun (c : α) => And (Ne.{succ u1} α c (OfNat.ofNat.{u1} α 0 (Zero.toOfNat0.{u1} α (MonoidWithZero.toZero.{u1} α (GroupWithZero.toMonoidWithZero.{u1} α _inst_1))))) (Eq.{succ u1} α (HMul.hMul.{u1, u1, u1} α α α (instHMul.{u1} α (MulZeroClass.toMul.{u1} α (MulZeroOneClass.toMulZeroClass.{u1} α (MonoidWithZero.toMulZeroOneClass.{u1} α (GroupWithZero.toMonoidWithZero.{u1} α _inst_1))))) (HMul.hMul.{u1, u1, u1} α α α (instHMul.{u1} α (MulZeroClass.toMul.{u1} α (MulZeroOneClass.toMulZeroClass.{u1} α (MonoidWithZero.toMulZeroOneClass.{u1} α (GroupWithZero.toMonoidWithZero.{u1} α _inst_1))))) c a) (Inv.inv.{u1} α (GroupWithZero.toInv.{u1} α _inst_1) c)) b)))
Case conversion may be inaccurate. Consider using '#align is_conj_iff₀ isConj_iff₀ₓ'. -/
@[simp]
theorem isConj_iff₀ [GroupWithZero α] {a b : α} : IsConj a b ↔ ∃ c : α, c ≠ 0 ∧ c * a * c⁻¹ = b :=
  ⟨fun ⟨c, hc⟩ =>
    ⟨c, by
      rw [← Units.val_inv_eq_inv_val, Units.mul_inv_eq_iff_eq_mul]
      exact ⟨c.ne_zero, hc⟩⟩,
    fun ⟨c, c0, hc⟩ =>
    ⟨Units.mk0 c c0,
      by
      rw [SemiconjBy, ← Units.mul_inv_eq_iff_eq_mul, Units.val_inv_eq_inv_val, Units.val_mk0]
      exact hc⟩⟩
#align is_conj_iff₀ isConj_iff₀

namespace IsConj

#print IsConj.setoid /-
/- This small quotient API is largely copied from the API of `associates`;
where possible, try to keep them in sync -/
/-- The setoid of the relation `is_conj` iff there is a unit `u` such that `u * x = y * u` -/
protected def setoid (α : Type _) [Monoid α] : Setoid α
    where
  R := IsConj
  iseqv := ⟨IsConj.refl, fun a b => IsConj.symm, fun a b c => IsConj.trans⟩
#align is_conj.setoid IsConj.setoid
-/

end IsConj

attribute [local instance] IsConj.setoid

#print ConjClasses /-
/-- The quotient type of conjugacy classes of a group. -/
def ConjClasses (α : Type _) [Monoid α] : Type _ :=
  Quotient (IsConj.setoid α)
#align conj_classes ConjClasses
-/

namespace ConjClasses

section Monoid

variable [Monoid α] [Monoid β]

#print ConjClasses.mk /-
/-- The canonical quotient map from a monoid `α` into the `conj_classes` of `α` -/
protected def mk {α : Type _} [Monoid α] (a : α) : ConjClasses α :=
  ⟦a⟧
#align conj_classes.mk ConjClasses.mk
-/

instance : Inhabited (ConjClasses α) :=
  ⟨⟦1⟧⟩

#print ConjClasses.mk_eq_mk_iff_isConj /-
theorem mk_eq_mk_iff_isConj {a b : α} : ConjClasses.mk a = ConjClasses.mk b ↔ IsConj a b :=
  Iff.intro Quotient.exact Quot.sound
#align conj_classes.mk_eq_mk_iff_is_conj ConjClasses.mk_eq_mk_iff_isConj
-/

#print ConjClasses.quotient_mk_eq_mk /-
theorem quotient_mk_eq_mk (a : α) : ⟦a⟧ = ConjClasses.mk a :=
  rfl
#align conj_classes.quotient_mk_eq_mk ConjClasses.quotient_mk_eq_mk
-/

#print ConjClasses.quot_mk_eq_mk /-
theorem quot_mk_eq_mk (a : α) : Quot.mk Setoid.r a = ConjClasses.mk a :=
  rfl
#align conj_classes.quot_mk_eq_mk ConjClasses.quot_mk_eq_mk
-/

#print ConjClasses.forall_isConj /-
theorem forall_isConj {p : ConjClasses α → Prop} : (∀ a, p a) ↔ ∀ a, p (ConjClasses.mk a) :=
  Iff.intro (fun h a => h _) fun h a => Quotient.inductionOn a h
#align conj_classes.forall_is_conj ConjClasses.forall_isConj
-/

#print ConjClasses.mk_surjective /-
theorem mk_surjective : Function.Surjective (@ConjClasses.mk α _) :=
  forall_isConj.2 fun a => ⟨a, rfl⟩
#align conj_classes.mk_surjective ConjClasses.mk_surjective
-/

instance : One (ConjClasses α) :=
  ⟨⟦1⟧⟩

/- warning: conj_classes.one_eq_mk_one -> ConjClasses.one_eq_mk_one is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} [_inst_1 : Monoid.{u1} α], Eq.{succ u1} (ConjClasses.{u1} α _inst_1) (OfNat.ofNat.{u1} (ConjClasses.{u1} α _inst_1) 1 (OfNat.mk.{u1} (ConjClasses.{u1} α _inst_1) 1 (One.one.{u1} (ConjClasses.{u1} α _inst_1) (ConjClasses.hasOne.{u1} α _inst_1)))) (ConjClasses.mk.{u1} α _inst_1 (OfNat.ofNat.{u1} α 1 (OfNat.mk.{u1} α 1 (One.one.{u1} α (MulOneClass.toHasOne.{u1} α (Monoid.toMulOneClass.{u1} α _inst_1))))))
but is expected to have type
  forall {α : Type.{u1}} [_inst_1 : Monoid.{u1} α], Eq.{succ u1} (ConjClasses.{u1} α _inst_1) (OfNat.ofNat.{u1} (ConjClasses.{u1} α _inst_1) 1 (One.toOfNat1.{u1} (ConjClasses.{u1} α _inst_1) (ConjClasses.instOneConjClasses.{u1} α _inst_1))) (ConjClasses.mk.{u1} α _inst_1 (OfNat.ofNat.{u1} α 1 (One.toOfNat1.{u1} α (Monoid.toOne.{u1} α _inst_1))))
Case conversion may be inaccurate. Consider using '#align conj_classes.one_eq_mk_one ConjClasses.one_eq_mk_oneₓ'. -/
theorem one_eq_mk_one : (1 : ConjClasses α) = ConjClasses.mk 1 :=
  rfl
#align conj_classes.one_eq_mk_one ConjClasses.one_eq_mk_one

#print ConjClasses.exists_rep /-
theorem exists_rep (a : ConjClasses α) : ∃ a0 : α, ConjClasses.mk a0 = a :=
  Quot.exists_rep a
#align conj_classes.exists_rep ConjClasses.exists_rep
-/

#print ConjClasses.map /-
/-- A `monoid_hom` maps conjugacy classes of one group to conjugacy classes of another. -/
def map (f : α →* β) : ConjClasses α → ConjClasses β :=
  Quotient.lift (ConjClasses.mk ∘ f) fun a b ab => mk_eq_mk_iff_isConj.2 (f.map_isConj ab)
#align conj_classes.map ConjClasses.map
-/

/- warning: conj_classes.map_surjective -> ConjClasses.map_surjective is a dubious translation:
lean 3 declaration is
  forall {α : Type.{u1}} {β : Type.{u2}} [_inst_1 : Monoid.{u1} α] [_inst_2 : Monoid.{u2} β] {f : MonoidHom.{u1, u2} α β (Monoid.toMulOneClass.{u1} α _inst_1) (Monoid.toMulOneClass.{u2} β _inst_2)}, (Function.Surjective.{succ u1, succ u2} α β (coeFn.{max (succ u2) (succ u1), max (succ u1) (succ u2)} (MonoidHom.{u1, u2} α β (Monoid.toMulOneClass.{u1} α _inst_1) (Monoid.toMulOneClass.{u2} β _inst_2)) (fun (_x : MonoidHom.{u1, u2} α β (Monoid.toMulOneClass.{u1} α _inst_1) (Monoid.toMulOneClass.{u2} β _inst_2)) => α -> β) (MonoidHom.hasCoeToFun.{u1, u2} α β (Monoid.toMulOneClass.{u1} α _inst_1) (Monoid.toMulOneClass.{u2} β _inst_2)) f)) -> (Function.Surjective.{succ u1, succ u2} (ConjClasses.{u1} α _inst_1) (ConjClasses.{u2} β _inst_2) (ConjClasses.map.{u1, u2} α β _inst_1 _inst_2 f))
but is expected to have type
  forall {α : Type.{u1}} {β : Type.{u2}} [_inst_1 : Monoid.{u1} α] [_inst_2 : Monoid.{u2} β] {f : MonoidHom.{u1, u2} α β (Monoid.toMulOneClass.{u1} α _inst_1) (Monoid.toMulOneClass.{u2} β _inst_2)}, (Function.Surjective.{succ u1, succ u2} α β (FunLike.coe.{max (succ u1) (succ u2), succ u1, succ u2} (MonoidHom.{u1, u2} α β (Monoid.toMulOneClass.{u1} α _inst_1) (Monoid.toMulOneClass.{u2} β _inst_2)) α (fun (_x : α) => (fun (x._@.Mathlib.Algebra.Hom.Group._hyg.2391 : α) => β) _x) (MulHomClass.toFunLike.{max u1 u2, u1, u2} (MonoidHom.{u1, u2} α β (Monoid.toMulOneClass.{u1} α _inst_1) (Monoid.toMulOneClass.{u2} β _inst_2)) α β (MulOneClass.toMul.{u1} α (Monoid.toMulOneClass.{u1} α _inst_1)) (MulOneClass.toMul.{u2} β (Monoid.toMulOneClass.{u2} β _inst_2)) (MonoidHomClass.toMulHomClass.{max u1 u2, u1, u2} (MonoidHom.{u1, u2} α β (Monoid.toMulOneClass.{u1} α _inst_1) (Monoid.toMulOneClass.{u2} β _inst_2)) α β (Monoid.toMulOneClass.{u1} α _inst_1) (Monoid.toMulOneClass.{u2} β _inst_2) (MonoidHom.monoidHomClass.{u1, u2} α β (Monoid.toMulOneClass.{u1} α _inst_1) (Monoid.toMulOneClass.{u2} β _inst_2)))) f)) -> (Function.Surjective.{succ u1, succ u2} (ConjClasses.{u1} α _inst_1) (ConjClasses.{u2} β _inst_2) (ConjClasses.map.{u1, u2} α β _inst_1 _inst_2 f))
Case conversion may be inaccurate. Consider using '#align conj_classes.map_surjective ConjClasses.map_surjectiveₓ'. -/
theorem map_surjective {f : α →* β} (hf : Function.Surjective f) :
    Function.Surjective (ConjClasses.map f) :=
  by
  intro b
  obtain ⟨b, rfl⟩ := ConjClasses.mk_surjective b
  obtain ⟨a, rfl⟩ := hf b
  exact ⟨ConjClasses.mk a, rfl⟩
#align conj_classes.map_surjective ConjClasses.map_surjective

library_note "slow-failing instance priority"/--
Certain instances trigger further searches when they are considered as candidate instances;
these instances should be assigned a priority lower than the default of 1000 (for example, 900).

The conditions for this rule are as follows:
 * a class `C` has instances `instT : C T` and `instT' : C T'`
 * types `T` and `T'` are both specializations of another type `S`
 * the parameters supplied to `S` to produce `T` are not (fully) determined by `instT`,
   instead they have to be found by instance search
If those conditions hold, the instance `instT` should be assigned lower priority.

For example, suppose the search for an instance of `decidable_eq (multiset α)` tries the
candidate instance `con.quotient.decidable_eq (c : con M) : decidable_eq c.quotient`.
Since `multiset` and `con.quotient` are both quotient types, unification will check
that the relations `list.perm` and `c.to_setoid.r` unify. However, `c.to_setoid` depends on
a `has_mul M` instance, so this unification triggers a search for `has_mul (list α)`;
this will traverse all subclasses of `has_mul` before failing.
On the other hand, the search for an instance of `decidable_eq (con.quotient c)` for `c : con M`
can quickly reject the candidate instance `multiset.has_decidable_eq` because the type of
`list.perm : list ?m_1 → list ?m_1 → Prop` does not unify with `M → M → Prop`.
Therefore, we should assign `con.quotient.decidable_eq` a lower priority because it fails slowly.
(In terms of the rules above, `C := decidable_eq`, `T := con.quotient`,
`instT := con.quotient.decidable_eq`, `T' := multiset`, `instT' := multiset.has_decidable_eq`,
and `S := quot`.)

If the type involved is a free variable (rather than an instantiation of some type `S`),
the instance priority should be even lower, see Note [lower instance priority].
-/

-- see Note [slow-failing instance priority]
instance (priority := 900) [DecidableRel (IsConj : α → α → Prop)] : DecidableEq (ConjClasses α) :=
  Quotient.decidableEq

end Monoid

section CommMonoid

variable [CommMonoid α]

#print ConjClasses.mk_injective /-
theorem mk_injective : Function.Injective (@ConjClasses.mk α _) := fun _ _ =>
  (mk_eq_mk_iff_isConj.trans isConj_iff_eq).1
#align conj_classes.mk_injective ConjClasses.mk_injective
-/

#print ConjClasses.mk_bijective /-
theorem mk_bijective : Function.Bijective (@ConjClasses.mk α _) :=
  ⟨mk_injective, mk_surjective⟩
#align conj_classes.mk_bijective ConjClasses.mk_bijective
-/

#print ConjClasses.mkEquiv /-
/-- The bijection between a `comm_group` and its `conj_classes`. -/
def mkEquiv : α ≃ ConjClasses α :=
  ⟨ConjClasses.mk, Quotient.lift id fun (a : α) b => isConj_iff_eq.1, Quotient.lift_mk _ _,
    by
    rw [Function.RightInverse, Function.LeftInverse, forall_is_conj]
    intro x
    rw [← quotient_mk_eq_mk, ← quotient_mk_eq_mk, Quotient.lift_mk, id.def]⟩
#align conj_classes.mk_equiv ConjClasses.mkEquiv
-/

end CommMonoid

end ConjClasses

section Monoid

variable [Monoid α]

#print conjugatesOf /-
/-- Given an element `a`, `conjugates a` is the set of conjugates. -/
def conjugatesOf (a : α) : Set α :=
  { b | IsConj a b }
#align conjugates_of conjugatesOf
-/

#print mem_conjugatesOf_self /-
theorem mem_conjugatesOf_self {a : α} : a ∈ conjugatesOf a :=
  IsConj.refl _
#align mem_conjugates_of_self mem_conjugatesOf_self
-/

#print IsConj.conjugatesOf_eq /-
theorem IsConj.conjugatesOf_eq {a b : α} (ab : IsConj a b) : conjugatesOf a = conjugatesOf b :=
  Set.ext fun g => ⟨fun ag => ab.symm.trans ag, fun bg => ab.trans bg⟩
#align is_conj.conjugates_of_eq IsConj.conjugatesOf_eq
-/

#print isConj_iff_conjugatesOf_eq /-
theorem isConj_iff_conjugatesOf_eq {a b : α} : IsConj a b ↔ conjugatesOf a = conjugatesOf b :=
  ⟨IsConj.conjugatesOf_eq, fun h =>
    by
    have ha := mem_conjugatesOf_self
    rwa [← h] at ha⟩
#align is_conj_iff_conjugates_of_eq isConj_iff_conjugatesOf_eq
-/

end Monoid

namespace ConjClasses

variable [Monoid α]

attribute [local instance] IsConj.setoid

#print ConjClasses.carrier /-
/-- Given a conjugacy class `a`, `carrier a` is the set it represents. -/
def carrier : ConjClasses α → Set α :=
  Quotient.lift conjugatesOf fun (a : α) b ab => IsConj.conjugatesOf_eq ab
#align conj_classes.carrier ConjClasses.carrier
-/

#print ConjClasses.mem_carrier_mk /-
theorem mem_carrier_mk {a : α} : a ∈ carrier (ConjClasses.mk a) :=
  IsConj.refl _
#align conj_classes.mem_carrier_mk ConjClasses.mem_carrier_mk
-/

#print ConjClasses.mem_carrier_iff_mk_eq /-
theorem mem_carrier_iff_mk_eq {a : α} {b : ConjClasses α} : a ∈ carrier b ↔ ConjClasses.mk a = b :=
  by
  revert b
  rw [forall_is_conj]
  intro b
  rw [carrier, eq_comm, mk_eq_mk_iff_is_conj, ← quotient_mk_eq_mk, Quotient.lift_mk]
  rfl
#align conj_classes.mem_carrier_iff_mk_eq ConjClasses.mem_carrier_iff_mk_eq
-/

#print ConjClasses.carrier_eq_preimage_mk /-
theorem carrier_eq_preimage_mk {a : ConjClasses α} : a.carrier = ConjClasses.mk ⁻¹' {a} :=
  Set.ext fun x => mem_carrier_iff_mk_eq
#align conj_classes.carrier_eq_preimage_mk ConjClasses.carrier_eq_preimage_mk
-/

end ConjClasses

assert_not_exists Multiset


/-
Copyright (c) 2018 Mario Carneiro. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Mario Carneiro, Scott Morrison
-/
import Mathlib.PrePort
import Mathlib.Lean3Lib.init.default
import Mathlib.data.list.basic
import Mathlib.PostPort

universes u

namespace Mathlib

namespace list

/- bag_inter -/

@[simp] theorem nil_bag_inter {α : Type u} [DecidableEq α] (l : List α) : list.bag_inter [] l = [] :=
  list.cases_on l (Eq.refl (list.bag_inter [] []))
    fun (l_hd : α) (l_tl : List α) => Eq.refl (list.bag_inter [] (l_hd :: l_tl))

@[simp] theorem bag_inter_nil {α : Type u} [DecidableEq α] (l : List α) : list.bag_inter l [] = [] :=
  list.cases_on l (Eq.refl (list.bag_inter [] []))
    fun (l_hd : α) (l_tl : List α) => Eq.refl (list.bag_inter (l_hd :: l_tl) [])

@[simp] theorem cons_bag_inter_of_pos {α : Type u} [DecidableEq α] {a : α} (l₁ : List α) {l₂ : List α} (h : a ∈ l₂) : list.bag_inter (a :: l₁) l₂ = a :: list.bag_inter l₁ (list.erase l₂ a) :=
  list.cases_on l₂ (fun (h : a ∈ []) => if_pos h) (fun (l₂_hd : α) (l₂_tl : List α) (h : a ∈ l₂_hd :: l₂_tl) => if_pos h)
    h

@[simp] theorem cons_bag_inter_of_neg {α : Type u} [DecidableEq α] {a : α} (l₁ : List α) {l₂ : List α} (h : ¬a ∈ l₂) : list.bag_inter (a :: l₁) l₂ = list.bag_inter l₁ l₂ := sorry

@[simp] theorem mem_bag_inter {α : Type u} [DecidableEq α] {a : α} {l₁ : List α} {l₂ : List α} : a ∈ list.bag_inter l₁ l₂ ↔ a ∈ l₁ ∧ a ∈ l₂ := sorry

@[simp] theorem count_bag_inter {α : Type u} [DecidableEq α] {a : α} {l₁ : List α} {l₂ : List α} : count a (list.bag_inter l₁ l₂) = min (count a l₁) (count a l₂) := sorry

theorem bag_inter_sublist_left {α : Type u} [DecidableEq α] (l₁ : List α) (l₂ : List α) : list.bag_inter l₁ l₂ <+ l₁ := sorry

theorem bag_inter_nil_iff_inter_nil {α : Type u} [DecidableEq α] (l₁ : List α) (l₂ : List α) : list.bag_inter l₁ l₂ = [] ↔ l₁ ∩ l₂ = [] := sorry