-- import tactic
/-
Step 1:
example (p q r : Prop) : p → (q ∧ r) → p ∧ q :=
assume (h₁ : p)(h₂ : q ∧ r),
_

p q r : Prop,
h₁ : p,
h₂ : q ∧ r
⊢ p ∧ q

Step 2:
example (p q r : Prop) : p → (q ∧ r) → p ∧ q :=
assume (h₁ : p)(h₂ : q ∧ r),
have h₃ : q, from and.left h₂,
_

p q r : Prop,
h₁ : p,
h₂ : q ∧ r,
h₃ : q
⊢ p ∧ q

Step 3:
example (p q r : Prop) : p → (q ∧ r) → p ∧ q :=
assume (h₁ : p)(h₂ : q ∧ r),
have h₃ : q, from and.left h₂,
show _, from and.intro _ _

p q r : Prop,
h₁ : p,
h₂ : q ∧ r,
h₃ : q
⊢ p <=> h₁
⊢ q <=> h₃

-/

example (p q r : Prop) : p → (q ∧ r) → p ∧ q :=
assume (h₁ : p)(h₂ : q ∧ r),
have h₃ : q, from and.left h₂,
show p ∧ q, from and.intro h₁ h₃

example (p q r : Prop) : p → (q ∧ r) → p ∧ q :=
assume : p,
assume : (q ∧ r),
have q, from and.left this,
show p ∧ q, from and.intro ‹p› this -- ‹ = \f, › = \frq

-- ‹p› means (by assumption : p)
-- ‹_› means (by assumption : _) means (by assumption)

example (p q r : Prop) : p → (q ∧ r) → p ∧ q :=
assume : p,
assume : (q ∧ r),
have q, from and.left this,
show p ∧ q, from and.intro (by assumption : p) this

/-
example (p q r : Prop) : p → (q ∧ r) → p ∧ q :=
assume (h₁ : p) (h₂ : q ∧ r),
suffices h₃ : q, from _

p q r : Prop,
h₁ : p,
h₂ : q ∧ r,
h₃ : q
⊢ p ∧ q

example (p q r : Prop) : p → (q ∧ r) → p ∧ q :=
assume (h₁ : p) (h₂ : q ∧ r),
suffices h₃ : q, from and.intro h₁ h₃,
_

p q r : Prop,
h₁ : p,
h₂ : q ∧ r
⊢ q <=> h₂.left
-/

example (p q r : Prop) : p → (q ∧ r) → p ∧ q :=
assume (h₁ : p) (h₂ : q ∧ r),
suffices h₃ : q, from and.intro h₁ h₃,
show q, from h₂.left

/-
Lean also supports calculational environment, which is
introduced with the keyword calc. The syntax is as follows:
calc
  <expr>_0 'op_1' <expr>_1 ':' <proof>_1
     '...' 'op_2' <expr>_2 ':' <proof>_2
     ...
     '...' 'op_n' <expr>_n ':' <proof>_n
-/

variables (a b c d e : ℕ)
variable h1 : a = b
axiom h2 : b = c + 1
variable h3 : c = d
variable h4 : e = 1 + d

/-
Step 1:
theorem T : a = e :=
calc
  a   = b     : _ -- ⊢ a = b
  ... = c + 1 : _ -- ⊢ b = c + 1
  ... = d + 1 : _ -- ⊢ c + 1 = d + 1
  ... = 1 + d : _ -- ⊢ d + 1 = 1 + d
  ... = e     : _ -- ⊢ 1 + d = e

Step 2:
-/

-- Equivalent ways of writing proof:
-- 1. add_comm d _
-- 2. by exact add_comm d _
-- 3. by { exact add_comm d _, }
-- 4. begin exact add_comm d _, end

theorem T : a = e :=
calc
  a   = b     : h1 -- ⊢ a = b
  ... = c + 1 : h2 b c -- ⊢ b = c + 1
  ... = d + 1 : congr_arg nat.succ h3 -- ⊢ c + 1 = d + 1
  ... = 1 + d : add_comm d _ -- ⊢ d + 1 = 1 + d, _ <=> (1 : ℕ)
  ... = e     : h4.symm -- ⊢ 1 + d = e

-- congr_arg : ∀ {α β : Type} {a₁ a₂ : α}
--   (f : α → β), a₁ = a₂ → f a₁ = f a₂

-- add_comm : ∀ {α : Type} [_inst_1 : add_comm_semigroup α]
--   (a b : α), a + b = b + a

-- eq.symm : ∀ {α : Type} {a b : α},
--   a = b → b = a
/-
meta def f : ℕ → bool
| 0 :=  bor (f 1) (f 2)
| (nat.succ n) := f n
-/

constant f : nat → bool

-- Lean: invalid definition, it uses untrusted declaration 'f'
axiom f₀ : f 0 = bor (f 1) (f 2)
axiom f_ind : ∀ n, n > 0 → f (n + 1) = f n

-- let f 0 = false => f 1 ∨ f 2 = false => f 1 = false, f 2 = false
-- ∀ n, n > 0 → f (n + 1) = f n => f 3 = f 2 = false, f 4 = f 3 = false, ... , f (n : nat, n > 0) = false = f 0

-- let f 0 = true => f 1 ∨ f 2 = true
-- ∀ n, n > 0 → f (n + 1) = f n => f 2 = f 1 => f 1 ∨ f 1 = true => f 1 = true => f 2 = true => ... => f (n : nat, n > 0) = true = f 0

-- => lemma ∀ n, n > 0 → f n = f 0

set_option trace.simplify.rewrite true

lemma fn_eq_f0 : ∀ n, n > 0 → f n = f 0 :=
begin
  intros n n_gt_0,
  have h : n > 0 → n = 1 ∨ n > 1 := sorry,
  cases (h n_gt_0) with h_n_eq_1 n_gt_1,
  rw h_n_eq_1,
  have h₀ : f 0 = tt ∨ f 0 = ff := sorry,
  cases h₀,
  rw f₀,
  suffices f₂_true : f 2 = tt,
  rw f₂_true,
  -- simp, -- [simplify.rewrite] [bor_tt]: f 1 || tt ==> tt
  rw bor_tt,
  by_contradiction H,
  -- simp at H, -- [simplify.rewrite] [eq_ff_eq_not_eq_tt]: ¬f 1 = tt ==> f 1 = ff
  rw eq_ff_eq_not_eq_tt at H,
  have ind₁ := f_ind 1,
  change 1 > _ → f 2 = f 1 at ind₁,
  have one_gt_zero : 1 > 0 := sorry,
  have f₁_eq_f₂ := ind₁ one_gt_zero, clear ind₁,
  rw f₁_eq_f₂ at f₂_true,
  rw f₂_true at H,
  contradiction,

  all_goals { sorry, },
end

/-
kernel failed to type check declaration 'fn_eq_f0' this is usually due to a buggy tactic or a bug in the builtin elaborator
elaborated type:
  ∀ (n : ℕ), n > 0 → f n = f 0
elaborated value:
  λ (n : ℕ) (n_gt_0 : n > 0), sorry
nested exception message:
invalid definition, it uses untrusted declaration 'f'
-/
-- ************************************************************************************************************************************
-- axiom f₀ : f 0 = bor (f 1) (f 2)
-- axiom f_ind : ∀ n, n > 0 → f (n + 1) = f n

-- let f 0 = false => f 1 ∨ f 2 = false => f 1 = false, f 2 = false
-- ∀ n, n > 0 → f (n + 1) = f n => f 3 = f 2 = false, f 4 = f 3 = false, ... , f (n : nat, n > 0) = false = f 0

-- let f 0 = true => f 1 ∨ f 2 = true
-- ∀ n, n > 0 → f (n + 1) = f n => f 2 = f 1 => f 1 ∨ f 1 = true => f 1 = true => f 2 = true => ... => f (n : nat, n > 0) = true = f 0

-- => lemma ∀ n, n > 0 → f n = f 0
-- ************************************************************************************************************************************

-- ih : n > 0 → f n = f 0
-- ⊢ n.succ > 0 → f n.succ = f 0
lemma induction_lemma_false_case (f₀_false : f 0 = ff) (f₁_false : f 1 = ff)
  (n : ℕ): (n > 0 → f n = f 0) → n.succ > 0 → f n.succ = f 0 := sorry

lemma fn_eq_f0'' : ∀ n, n > 0 → f n = f 0 :=
begin
  intro n,
  have bool_f₀ : f 0 = ff ∨ f 0 = tt, from sorry,
  cases bool_f₀ with f₀_false f₀_true,
  { -- f₀_false : f 0 = ff
    have H : bor (f 1) (f 2) = ff, {
      rw <-f₀_false,
      apply f₀.symm,
    },
    have bool_lemma : ∀ (a b : bool), a || b = ff = (a = ff ∧ b = ff), {
      intros a b,
      -- simp, -- [bor_eq_false_eq_eq_ff_and_eq_ff]: a || b = ff ==> a = ff ∧ b = ff
      rw bor_eq_false_eq_eq_ff_and_eq_ff,
      -- library_search, -- not return a result, still calculating...
    },
    have h2 := bool_lemma (f 1) (f 2),
    rw h2 at H,
    type_check H.1, -- f 1 = ff
    type_check H.2, -- f 2 = ff
    have f₁_false := H.1, have f₂_false := H.2, clear H h2 bool_lemma,

    induction n with n ih,
    have h := eq.refl (f 0),
    -- have false_imp : ∀ (a : Prop),  false → a, {
    --   intro a,
    --   rw false_implies_iff, trivial,
    -- },
    have zero_ge_zero_is_false : 0 > 0 = false, {
      -- apply _,
      sorry,
    },
    rw zero_ge_zero_is_false,
    intro, exfalso, exact a,
    exact induction_lemma_false_case f₀_false f₁_false n ih,
  },
  { -- f₀_true : f 0 = tt
    sorry,
  },
end

lemma fn_eq_f0' : ∀ n, n > 0 → f n = f 0 :=
begin
  intro n,
  induction n with n ih,
  case nat.zero {
    -- ⊢ 0 > 0 → f 0 = f 0
    simp,
    -- [nat.nat_zero_eq_zero]: 0 ==> 0
    -- [eq_self_iff_true]: f 0 = f 0 ==> true
    -- [implies_true_iff]: 0 > 0 → true ==> true
  },
  case nat.succ {
    -- ⊢ n.succ > 0 → f n.succ = f 0
    have bool_f₀ : f 0 = tt ∨ f 0 = ff, from sorry,
    cases bool_f₀ with f₀_true f₀_false,
    case or.inl {
      rw f₀_true,
      rw f₀_true at ih,
      sorry,
    },
    case or.inr {
      -- rw f₀_false,
      rw f₀_false at ih,
      intro h_nsucc,
      -- have H: f n = ff → f n.succ = ff, sorry,
      have H := f_ind n,
      change n > 0 → f n.succ = f n at H,
      have h_gt_nat : n = 0 ∨ n > 0, sorry,
      cases h_gt_nat,
      case or.inl {
        rw h_gt_nat,
        sorry,
      },
      case or.inr {
        sorry,
      },
    },
  },
end


import algebra.group.defs
import logic.function.basic
import algebra.group.basic

section group

variables {M : Type} [mul_one_class M]

lemma eq_one_iff_eq_one_of_mul_eq_one' {a b : M} (h : a * b = 1) : a = 1 ↔ b = 1 :=
  by split; { rintro rfl, simpa using h}

lemma one_mul_eq_id' : ((*) (1 : M)) = id := funext one_mul
/-
begin
  funext x,
  simp only [one_mul, id.def],
end
-/

variables {G : Type} [group G] {a b c : G}

lemma inv_mul_cancel_right' (a b : G) : a * b⁻¹ * b = a := -- by simp [mul_assoc]
begin
  simp [mul_assoc],
end

theorem mul_right_surjective' (a : G) : function.surjective (λ x, x * a) :=
  λ x, ⟨x * a⁻¹, inv_mul_cancel_right x a⟩

/-
begin
  intros x,
  use (x * a⁻¹),
  apply inv_mul_cancel_right,
end
-/

theorem inv_eq_one' : a⁻¹ = 1 ↔ a = 1 :=
begin
  rw [← @inv_inj _ _ a 1, one_inv],
end
-- by rw [← @inv_inj _ _ a 1, one_inv]

end group

section add_group

variables {G : Type} [add_group G] {a b c d : G}

lemma eq_of_sub_eq_zero' (h : a - b = 0) : a = b :=
calc a = a - b + b : (sub_add_cancel a b).symm
   ... = b         : by rw [h, zero_add]

#check (sub_add_cancel a b).symm

example : a = a - b + b :=
begin
  apply (sub_add_cancel a b).symm
end

lemma eq_of_sub_eq_zero'' (h : a - b = 0) : a = b :=
calc a = a - b + b : (sub_add_cancel a b).symm
  ...  = b         : by rw [h, zero_add]

end add_group


import group_theory.coset set_theory.cardinal data.fintype.basic

variables (G : Type*) [set G][group G]

def subgroup_index (H:subgroup G) := cardinal.mk (quotient_group.quotient H)

lemma index_in_finite_group_is_finite [h_fin : fintype G] (H: subgroup G)[h_dec : decidable_eq (quotient_group.quotient H)]:
fintype (quotient_group.quotient H):=

begin
    apply fintype.of_surjective quotient_group.mk,
    intro b,
    apply quot.exists_rep,
    exact h_dec,
    exact h_fin,
end


/-
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
-/

import probability.variance

/-!
# Moments and moment generating function

## Main definitions

* `probability_theory.moment X p μ`: `p`th moment of a real random variable `X` with respect to
  measure `μ`, `μ[X^p]`
* `probability_theory.central_moment X p μ`:`p`th central moment of `X` with respect to measure `μ`,
  `μ[(X - μ[X])^p]`
* `probability_theory.mgf X μ t`: moment generating function of `X` with respect to measure `μ`,
  `μ[exp(t*X)]`
* `probability_theory.cgf X μ t`: cumulant generating function, logarithm of the moment generating
  function

## Main results

* `probability_theory.indep_fun.mgf_add`: if two real random variables `X` and `Y` are independent
  and their mgf are defined at `t`, then `mgf (X + Y) μ t = mgf X μ t * mgf Y μ t`
* `probability_theory.indep_fun.cgf_add`: if two real random variables `X` and `Y` are independent
  and their mgf are defined at `t`, then `cgf (X + Y) μ t = cgf X μ t + cgf Y μ t`
* `probability_theory.measure_ge_le_exp_cgf` and `probability_theory.measure_le_le_exp_cgf`:
  Chernoff bound on the upper (resp. lower) tail of a random variable. For `t` nonnegative such that
  the cgf exists, `ℙ(ε ≤ X) ≤ exp(- t*ε + cgf X ℙ t)`. See also
  `probability_theory.measure_ge_le_exp_mul_mgf` and
  `probability_theory.measure_le_le_exp_mul_mgf` for versions of these results using `mgf` instead
  of `cgf`.

-/

open measure_theory filter finset real

noncomputable theory

open_locale big_operators measure_theory probability_theory ennreal nnreal

namespace probability_theory

variables {Ω ι : Type*} {m : measurable_space Ω} {X : Ω → ℝ} {p : ℕ} {μ : measure Ω}

include m

/-- Moment of a real random variable, `μ[X ^ p]`. -/
def moment (X : Ω → ℝ) (p : ℕ) (μ : measure Ω) : ℝ := μ[X ^ p]

/-- Central moment of a real random variable, `μ[(X - μ[X]) ^ p]`. -/
def central_moment (X : Ω → ℝ) (p : ℕ) (μ : measure Ω) : ℝ := μ[(X - (λ x, μ[X])) ^ p]

@[simp] lemma moment_zero (hp : p ≠ 0) : moment 0 p μ = 0 :=
by simp only [moment, hp, zero_pow', ne.def, not_false_iff, pi.zero_apply, integral_const,
  algebra.id.smul_eq_mul, mul_zero]

@[simp] lemma central_moment_zero (hp : p ≠ 0) : central_moment 0 p μ = 0 :=
by simp only [central_moment, hp, pi.zero_apply, integral_const, algebra.id.smul_eq_mul,
  mul_zero, zero_sub, pi.pow_apply, pi.neg_apply, neg_zero, zero_pow', ne.def, not_false_iff]

lemma central_moment_one' [is_finite_measure μ] (h_int : integrable X μ) :
  central_moment X 1 μ = (1 - (μ set.univ).to_real) * μ[X] :=
begin
  simp only [central_moment, pi.sub_apply, pow_one],
  rw integral_sub h_int (integrable_const _),
  simp only [sub_mul, integral_const, algebra.id.smul_eq_mul, one_mul],
end

@[simp] lemma central_moment_one [is_probability_measure μ] : central_moment X 1 μ = 0 :=
begin
  by_cases h_int : integrable X μ,
  { rw central_moment_one' h_int,
    simp only [measure_univ, ennreal.one_to_real, sub_self, zero_mul], },
  { simp only [central_moment, pi.sub_apply, pow_one],
    have : ¬ integrable (λ x, X x - integral μ X) μ,
    { refine λ h_sub, h_int _,
      have h_add : X = (λ x, X x - integral μ X) + (λ x, integral μ X),
      { ext1 x, simp, },
      rw h_add,
      exact h_sub.add (integrable_const _), },
    rw integral_undef this, },
end

lemma central_moment_two_eq_variance [is_finite_measure μ] (hX : mem_ℒp X 2 μ) :
  central_moment X 2 μ = variance X μ :=
by { rw hX.variance_eq, refl, }

section moment_generating_function

variables {t : ℝ}

/-- Moment generating function of a real random variable `X`: `λ t, μ[exp(t*X)]`. -/
def mgf (X : Ω → ℝ) (μ : measure Ω) (t : ℝ) : ℝ := μ[λ ω, exp (t * X ω)]

/-- Cumulant generating function of a real random variable `X`: `λ t, log μ[exp(t*X)]`. -/
def cgf (X : Ω → ℝ) (μ : measure Ω) (t : ℝ) : ℝ := log (mgf X μ t)

@[simp] lemma mgf_zero_fun : mgf 0 μ t = (μ set.univ).to_real :=
by simp only [mgf, pi.zero_apply, mul_zero, exp_zero, integral_const, algebra.id.smul_eq_mul,
  mul_one]

@[simp] lemma cgf_zero_fun : cgf 0 μ t = log (μ set.univ).to_real :=
by simp only [cgf, mgf_zero_fun]

@[simp] lemma mgf_zero_measure : mgf X (0 : measure Ω) t = 0 :=
by simp only [mgf, integral_zero_measure]

@[simp] lemma cgf_zero_measure : cgf X (0 : measure Ω) t = 0 :=
by simp only [cgf, log_zero, mgf_zero_measure]

@[simp] lemma mgf_const' (c : ℝ) : mgf (λ _, c) μ t = (μ set.univ).to_real * exp (t * c) :=
by simp only [mgf, integral_const, algebra.id.smul_eq_mul]

@[simp] lemma mgf_const (c : ℝ) [is_probability_measure μ] : mgf (λ _, c) μ t = exp (t * c) :=
by simp only [mgf_const', measure_univ, ennreal.one_to_real, one_mul]

@[simp] lemma cgf_const' [is_finite_measure μ] (hμ : μ ≠ 0) (c : ℝ) :
  cgf (λ _, c) μ t = log (μ set.univ).to_real + t * c :=
begin
  simp only [cgf, mgf_const'],
  rw log_mul _ (exp_pos _).ne',
  { rw log_exp _, },
  { rw [ne.def, ennreal.to_real_eq_zero_iff, measure.measure_univ_eq_zero],
    simp only [hμ, measure_ne_top μ set.univ, or_self, not_false_iff], },
end

@[simp] lemma cgf_const [is_probability_measure μ] (c : ℝ) : cgf (λ _, c) μ t = t * c :=
by simp only [cgf, mgf_const, log_exp]

@[simp] lemma mgf_zero' : mgf X μ 0 = (μ set.univ).to_real :=
by simp only [mgf, zero_mul, exp_zero, integral_const, algebra.id.smul_eq_mul, mul_one]

@[simp] lemma mgf_zero [is_probability_measure μ] : mgf X μ 0 = 1 :=
by simp only [mgf_zero', measure_univ, ennreal.one_to_real]

@[simp] lemma cgf_zero' : cgf X μ 0 = log (μ set.univ).to_real :=
by simp only [cgf, mgf_zero']

@[simp] lemma cgf_zero [is_probability_measure μ] : cgf X μ 0 = 0 :=
by simp only [cgf_zero', measure_univ, ennreal.one_to_real, log_one]

lemma mgf_undef (hX : ¬ integrable (λ ω, exp (t * X ω)) μ) : mgf X μ t = 0 :=
by simp only [mgf, integral_undef hX]

lemma cgf_undef (hX : ¬ integrable (λ ω, exp (t * X ω)) μ) : cgf X μ t = 0 :=
by simp only [cgf, mgf_undef hX, log_zero]

lemma mgf_nonneg : 0 ≤ mgf X μ t :=
begin
  refine integral_nonneg _,
  intro ω,
  simp only [pi.zero_apply],
  exact (exp_pos _).le,
end

lemma mgf_pos' (hμ : μ ≠ 0) (h_int_X : integrable (λ ω, exp (t * X ω)) μ) : 0 < mgf X μ t :=
begin
  simp_rw mgf,
  have : ∫ (x : Ω), exp (t * X x) ∂μ = ∫ (x : Ω) in set.univ, exp (t * X x) ∂μ,
  { simp only [measure.restrict_univ], },
  rw [this, set_integral_pos_iff_support_of_nonneg_ae _ _],
  { have h_eq_univ : function.support (λ (x : Ω), exp (t * X x)) = set.univ,
    { ext1 x,
      simp only [function.mem_support, set.mem_univ, iff_true],
      exact (exp_pos _).ne', },
    rw [h_eq_univ, set.inter_univ _],
    refine ne.bot_lt _,
    simp only [hμ, ennreal.bot_eq_zero, ne.def, measure.measure_univ_eq_zero, not_false_iff], },
  { refine eventually_of_forall (λ x, _),
    rw pi.zero_apply,
    exact (exp_pos _).le, },
  { rwa integrable_on_univ, },
end

lemma mgf_pos [is_probability_measure μ] (h_int_X : integrable (λ ω, exp (t * X ω)) μ) :
  0 < mgf X μ t :=
mgf_pos' (is_probability_measure.ne_zero μ) h_int_X

lemma mgf_neg : mgf (-X) μ t = mgf X μ (-t) :=
by simp_rw [mgf, pi.neg_apply, mul_neg, neg_mul]

lemma cgf_neg : cgf (-X) μ t = cgf X μ (-t) := by simp_rw [cgf, mgf_neg]

/-- This is a trivial application of `indep_fun.comp` but it will come up frequently. -/
lemma indep_fun.exp_mul {X Y : Ω → ℝ} (h_indep : indep_fun X Y μ) (s t : ℝ) :
  indep_fun (λ ω, exp (s * X ω)) (λ ω, exp (t * Y ω)) μ :=
begin
  have h_meas : ∀ t, measurable (λ x, exp (t * x)) := λ t, (measurable_id'.const_mul t).exp,
  change indep_fun ((λ x, exp (s * x)) ∘ X) ((λ x, exp (t * x)) ∘ Y) μ,
  exact indep_fun.comp h_indep (h_meas s) (h_meas t),
end

lemma indep_fun.mgf_add' {X Y : Ω → ℝ} (h_indep : indep_fun X Y μ)
  (hX : ae_strongly_measurable X μ) (hY : ae_strongly_measurable Y μ) :
  mgf (X + Y) μ t = mgf X μ t * mgf Y μ t :=
begin
  have A : continuous (λ (x : ℝ), exp (t * x)), by continuity,
  have h'X : ae_strongly_measurable (λ ω, exp (t * X ω)) μ :=
    A.ae_strongly_measurable.comp_ae_measurable hX.ae_measurable,
  have h'Y : ae_strongly_measurable (λ ω, exp (t * Y ω)) μ :=
    A.ae_strongly_measurable.comp_ae_measurable hY.ae_measurable,
  exact h_indep.mgf_add h'X h'Y
end

lemma indep_fun.cgf_add {X Y : Ω → ℝ} (h_indep : indep_fun X Y μ)
  (h_int_X : integrable (λ ω, exp (t * X ω)) μ)
  (h_int_Y : integrable (λ ω, exp (t * Y ω)) μ) :
  cgf (X + Y) μ t = cgf X μ t + cgf Y μ t :=
begin
  by_cases hμ : μ = 0,
  { simp [hμ], },
  simp only [cgf, h_indep.mgf_add h_int_X.ae_strongly_measurable h_int_Y.ae_strongly_measurable],
  exact log_mul (mgf_pos' hμ h_int_X).ne' (mgf_pos' hμ h_int_Y).ne',
end

lemma ae_strongly_measurable_exp_mul_add {X Y : Ω → ℝ}
  (h_int_X : ae_strongly_measurable (λ ω, exp (t * X ω)) μ)
  (h_int_Y : ae_strongly_measurable (λ ω, exp (t * Y ω)) μ) :
  ae_strongly_measurable (λ ω, exp (t * (X + Y) ω)) μ :=
begin
  simp_rw [pi.add_apply, mul_add, exp_add],
  exact ae_strongly_measurable.mul h_int_X h_int_Y,
end

lemma ae_strongly_measurable_exp_mul_sum {X : ι → Ω → ℝ} {s : finset ι}
  (h_int : ∀ i ∈ s, ae_strongly_measurable (λ ω, exp (t * X i ω)) μ) :
  ae_strongly_measurable (λ ω, exp (t * (∑ i in s, X i) ω)) μ :=
begin
  classical,
  induction s using finset.induction_on with i s hi_notin_s h_rec h_int,
  { simp only [pi.zero_apply, sum_apply, sum_empty, mul_zero, exp_zero],
    exact ae_strongly_measurable_const, },
  { have : ∀ (i : ι), i ∈ s → ae_strongly_measurable (λ (ω : Ω), exp (t * X i ω)) μ,
      from λ i hi, h_int i (mem_insert_of_mem hi),
    specialize h_rec this,
    rw sum_insert hi_notin_s,
    apply ae_strongly_measurable_exp_mul_add (h_int i (mem_insert_self _ _)) h_rec }
end

lemma indep_fun.integrable_exp_mul_add {X Y : Ω → ℝ} (h_indep : indep_fun X Y μ)
  (h_int_X : integrable (λ ω, exp (t * X ω)) μ)
  (h_int_Y : integrable (λ ω, exp (t * Y ω)) μ) :
  integrable (λ ω, exp (t * (X + Y) ω)) μ :=
begin
  simp_rw [pi.add_apply, mul_add, exp_add],
  exact (h_indep.exp_mul t t).integrable_mul h_int_X h_int_Y,
end

lemma Indep_fun.integrable_exp_mul_sum [is_probability_measure μ]
  {X : ι → Ω → ℝ} (h_indep : Indep_fun (λ i, infer_instance) X μ) (h_meas : ∀ i, measurable (X i))
  {s : finset ι} (h_int : ∀ i ∈ s, integrable (λ ω, exp (t * X i ω)) μ) :
  integrable (λ ω, exp (t * (∑ i in s, X i) ω)) μ :=
begin
  classical,
  induction s using finset.induction_on with i s hi_notin_s h_rec h_int,
  { simp only [pi.zero_apply, sum_apply, sum_empty, mul_zero, exp_zero],
    exact integrable_const _, },
  { have : ∀ (i : ι), i ∈ s → integrable (λ (ω : Ω), exp (t * X i ω)) μ,
      from λ i hi, h_int i (mem_insert_of_mem hi),
    specialize h_rec this,
    rw sum_insert hi_notin_s,
    refine indep_fun.integrable_exp_mul_add _ (h_int i (mem_insert_self _ _)) h_rec,
    exact (h_indep.indep_fun_finset_sum_of_not_mem h_meas hi_notin_s).symm, },
end

lemma Indep_fun.mgf_sum [is_probability_measure μ]
  {X : ι → Ω → ℝ} (h_indep : Indep_fun (λ i, infer_instance) X μ) (h_meas : ∀ i, measurable (X i))
  (s : finset ι) :
  mgf (∑ i in s, X i) μ t = ∏ i in s, mgf (X i) μ t :=
begin
  classical,
  induction s using finset.induction_on with i s hi_notin_s h_rec h_int,
  { simp only [sum_empty, mgf_zero_fun, measure_univ, ennreal.one_to_real, prod_empty], },
  { have h_int' : ∀ (i : ι), ae_strongly_measurable (λ (ω : Ω), exp (t * X i ω)) μ,
      from λ i, ((h_meas i).const_mul t).exp.ae_strongly_measurable,
    rw [sum_insert hi_notin_s, indep_fun.mgf_add
          (h_indep.indep_fun_finset_sum_of_not_mem h_meas hi_notin_s).symm (h_int' i)
          (ae_strongly_measurable_exp_mul_sum (λ i hi, h_int' i)),
        h_rec, prod_insert hi_notin_s] }
end

lemma Indep_fun.cgf_sum [is_probability_measure μ]
  {X : ι → Ω → ℝ} (h_indep : Indep_fun (λ i, infer_instance) X μ) (h_meas : ∀ i, measurable (X i))
  {s : finset ι} (h_int : ∀ i ∈ s, integrable (λ ω, exp (t * X i ω)) μ) :
  cgf (∑ i in s, X i) μ t = ∑ i in s, cgf (X i) μ t :=
begin
  simp_rw cgf,
  rw ← log_prod _ _ (λ j hj, _),
  { rw h_indep.mgf_sum h_meas },
  { exact (mgf_pos (h_int j hj)).ne', },
end

/-- **Chernoff bound** on the upper tail of a real random variable. -/
lemma measure_ge_le_exp_mul_mgf [is_finite_measure μ] (ε : ℝ) (ht : 0 ≤ t)
  (h_int : integrable (λ ω, exp (t * X ω)) μ) :
  (μ {ω | ε ≤ X ω}).to_real ≤ exp (- t * ε) * mgf X μ t :=
begin
  cases ht.eq_or_lt with ht_zero_eq ht_pos,
  { rw ht_zero_eq.symm,
    simp only [neg_zero, zero_mul, exp_zero, mgf_zero', one_mul],
    rw ennreal.to_real_le_to_real (measure_ne_top μ _) (measure_ne_top μ _),
    exact measure_mono (set.subset_univ _), },
  calc (μ {ω | ε ≤ X ω}).to_real
      = (μ {ω | exp (t * ε) ≤ exp (t * X ω)}).to_real :
    begin
      congr' with ω,
      simp only [exp_le_exp, eq_iff_iff],
      exact ⟨λ h, mul_le_mul_of_nonneg_left h ht_pos.le, λ h, le_of_mul_le_mul_left h ht_pos⟩,
    end
  ... ≤ (exp (t * ε))⁻¹ * μ[λ ω, exp (t * X ω)] :
    begin
      have : exp (t * ε) * (μ {ω | exp (t * ε) ≤ exp (t * X ω)}).to_real
          ≤ μ[λ ω, exp (t * X ω)],
        from mul_meas_ge_le_integral_of_nonneg (λ x, (exp_pos _).le) h_int _,
      rwa [mul_comm (exp (t * ε))⁻¹, ← div_eq_mul_inv, le_div_iff' (exp_pos _)],
    end
  ... = exp (- t * ε) * mgf X μ t : by { rw [neg_mul, exp_neg], refl, },
end

/-- **Chernoff bound** on the lower tail of a real random variable. -/
lemma measure_le_le_exp_mul_mgf [is_finite_measure μ] (ε : ℝ) (ht : t ≤ 0)
  (h_int : integrable (λ ω, exp (t * X ω)) μ) :
  (μ {ω | X ω ≤ ε}).to_real ≤ exp (- t * ε) * mgf X μ t :=
begin
  rw [← neg_neg t, ← mgf_neg, neg_neg, ← neg_mul_neg (-t)],
  refine eq.trans_le _ (measure_ge_le_exp_mul_mgf (-ε) (neg_nonneg.mpr ht) _),
  { congr' with ω,
    simp only [pi.neg_apply, neg_le_neg_iff], },
  { simp_rw [pi.neg_apply, neg_mul_neg],
    exact h_int, },
end

/-- **Chernoff bound** on the upper tail of a real random variable. -/
lemma measure_ge_le_exp_cgf [is_finite_measure μ] (ε : ℝ) (ht : 0 ≤ t)
  (h_int : integrable (λ ω, exp (t * X ω)) μ) :
  (μ {ω | ε ≤ X ω}).to_real ≤ exp (- t * ε + cgf X μ t) :=
begin
  refine (measure_ge_le_exp_mul_mgf ε ht h_int).trans _,
  rw exp_add,
  exact mul_le_mul le_rfl (le_exp_log _) mgf_nonneg (exp_pos _).le,
end

/-- **Chernoff bound** on the lower tail of a real random variable. -/
lemma measure_le_le_exp_cgf [is_finite_measure μ] (ε : ℝ) (ht : t ≤ 0)
  (h_int : integrable (λ ω, exp (t * X ω)) μ) :
  (μ {ω | X ω ≤ ε}).to_real ≤ exp (- t * ε + cgf X μ t) :=
begin
  refine (measure_le_le_exp_mul_mgf ε ht h_int).trans _,
  rw exp_add,
  exact mul_le_mul le_rfl (le_exp_log _) mgf_nonneg (exp_pos _).le,
end

end moment_generating_function

end probability_theory


def MT {p : Prop} {q : Prop}: (((p -> q) ∧ ¬q) -> ¬p) :=
  fun h1 : ((p -> q) ∧ ¬q) =>
    (fun (h2 : p) => h1.right (h1.left h2))

--∀x(F(x) → G(x)), ∃x(H(x) ∧ ¬G(x)) ⊢ ∃x(H(x) ∧ ¬F(x))

variable(F G H : ℕ → Prop  )
variable(x y : ℕ )
variable(a b : Prop )

example (h1 : ∀ x : ℕ, F x → G x ) (h2 : ∃ x : ℕ, H x ∧ ¬G x )
  : ∃ x : ℕ, (H x ∧ ¬ F x) :=
have ⟨a,(ha: H a ∧ ¬G a)⟩ := h2
have proofnotfa := MT $ And.intro (h1 a) ha.right
⟨a, And.intro ha.left proofnotfa⟩

-- ∃x(F(x) ∧ ¬G(x)), ∀x(F(x) → H(x)) ⊢ ∃x(H(x) ∧ ¬G(x))

example (h1 : ∃ x : ℕ, F x ∧ ¬ G x) (h2 : ∀ x : ℕ, F x → H x)
  : ∃ x : ℕ, H x ∧ ¬G x :=

  have ⟨a, (ha: F a ∧ ¬G a )⟩ := h1
  have proofha := (h2 a) ha.left
  ⟨a, And.intro proofha ha.right⟩

  --∀x(F(x) → G(x)), ¬∃x(G(x) ∧ H(x)) ⊢ ¬∃x(H(x) ∧ F(x))

  example (h1 : ∀x : ℕ, F x → G x) (h2 : ¬∃ x : ℕ, G x ∧ H x)
    : ¬∃x : ℕ, H x ∧ F x :=
  fun (a1 : ∃x : ℕ, H x ∧ F x) =>
    have ⟨a, (ha: H a ∧ F a)⟩ := a1
    h2 ⟨a, And.intro ((h1 a) ha.right) ha.left ⟩

-- ¬∃x(F(x) ∧ G(x)), ∃x(H(x) ∧ F(x)) ⊢ ∃x(H(x) ∧ ¬G(x))
example (h1 : ¬∃ x : ℕ, F x ∧ G x) (h2 : ∃ x : ℕ, H x ∧ F x)
  : ∃ x : ℕ, H x ∧ ¬G x :=
have ⟨a, (ha: H a ∧ F a )⟩ := h2
have proofnga := λ(ga : G a) => h1 ⟨a, And.intro ha.right ga⟩
⟨a, And.intro ha.left proofnga⟩

-- !(a v b) |- !a ^ !b
example (h1 : ¬(a ∨ b)) : ¬a ∧ ¬b :=

have proofna := λ(a1 : a) => h1 $ Or.intro_left b a1
have proofnb := λ(a2 : b) => h1 $ Or.intro_right a a2

And.intro proofna proofnb

axiom DNE {p : Prop} : ¬¬p -> p

-- !(a ^ b) |- !a v !b
example(h1 : ¬(a ∧ b)) : ¬a ∨ ¬b :=
DNE λ(a1 : ¬(¬a ∨ ¬b)) =>
  have proofa := λ(a2 : ¬a) =>
    a1 $ Or.intro_left (¬b) a2
  have proofb := λ(a3 : ¬b) =>
    a1 $ Or.intro_right (¬a) a3
  h1 $ And.intro (DNE proofa) (DNE proofb)


/-
Copyright (c) 2021 Yourong Zang. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Yourong Zang

! This file was ported from Lean 3 source module geometry.euclidean.angle.unoriented.conformal
! leanprover-community/mathlib commit 46b633fd842bef9469441c0209906f6dddd2b4f5
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Analysis.Calculus.Conformal.NormedSpace
import Mathbin.Geometry.Euclidean.Angle.Unoriented.Basic

/-!
# Angles and conformal maps

This file proves that conformal maps preserve angles.

-/

namespace InnerProductGeometry

variable {E F : Type _}

variable [NormedAddCommGroup E] [NormedAddCommGroup F]

variable [InnerProductSpace ℝ E] [InnerProductSpace ℝ F]

theorem IsConformalMap.preserves_angle {f' : E →L[ℝ] F} (h : IsConformalMap f') (u v : E) :
    angle (f' u) (f' v) = angle u v :=
  by
  obtain ⟨c, hc, li, rfl⟩ := h
  exact (angle_smul_smul hc _ _).trans (li.angle_map _ _)
#align inner_product_geometry.is_conformal_map.preserves_angle InnerProductGeometry.IsConformalMap.preserves_angle

/-- If a real differentiable map `f` is conformal at a point `x`,
    then it preserves the angles at that point. -/
theorem ConformalAt.preserves_angle {f : E → F} {x : E} {f' : E →L[ℝ] F} (h : HasFderivAt f f' x)
    (H : ConformalAt f x) (u v : E) : angle (f' u) (f' v) = angle u v :=
  let ⟨f₁, h₁, c⟩ := H
  h₁.unique h ▸ IsConformalMap.preserves_angle c u v
#align inner_product_geometry.conformal_at.preserves_angle InnerProductGeometry.ConformalAt.preserves_angle

end InnerProductGeometry


/-
Copyright (c) 2022 Joseph Myers. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Joseph Myers
-/
import geometry.euclidean.angle.sphere
import geometry.euclidean.sphere.second_inter

/-!
# IMO 2019 Q2

In triangle `ABC`, point `A₁` lies on side `BC` and point `B₁` lies on side `AC`. Let `P` and
`Q` be points on segments `AA₁` and `BB₁`, respectively, such that `PQ` is parallel to `AB`.
Let `P₁` be a point on line `PB₁`, such that `B₁` lies strictly between `P` and `P₁`, and
`∠PP₁C = ∠BAC`. Similarly, let `Q₁` be a point on line `QA₁`, such that `A₁` lies strictly
between `Q` and `Q₁`, and `∠CQ₁Q = ∠CBA`.

Prove that points `P`, `Q`, `P₁`, and `Q₁` are concyclic.

We follow Solution 1 from the
[official solutions](https://www.imo2019.uk/wp-content/uploads/2018/07/solutions-r856.pdf).
Letting the rays `AA₁` and `BB₁` intersect the circumcircle of `ABC` at `A₂` and `B₂`
respectively, we show with an angle chase that `P`, `Q`, `A₂`, `B₂` are concyclic and let `ω` be
the circle through those points. We then show that `C`, `Q₁`, `A₂`, `A₁` are concyclic, and
then that `Q₁` lies on `ω`, and similarly that `P₁` lies on `ω`, so the required four points are
concyclic.

Note that most of the formal proof is actually proving nondegeneracy conditions needed for that
angle chase / concyclicity argument, where an informal solution doesn't discuss those conditions
at all. Also note that (as described in `geometry.euclidean.angle.oriented.basic`) the oriented
angles used are modulo `2 * π`, so parts of the angle chase that are only valid for angles modulo
`π` (as used in the informal solution) are represented as equalities of twice angles, which we write
as `(2 : ℤ) • ∡ _ _ _ = (2 : ℤ) • _ _ _`.
-/

/--
We apply the following conventions for formalizing IMO geometry problems. A problem is assumed
to take place in the plane unless that is clearly not intended, so it is not required to prove
that the points are coplanar (whether or not that in fact follows from the other conditions).
Angles in problem statements are taken to be unoriented. A reference to an angle `∠XYZ` is taken
to imply that `X` and `Z` are not equal to `Y`, since choices of junk values play no role in
informal mathematics, and those implications are included as hypotheses for the problem whether
or not they follow from the other hypotheses. Similar, a reference to `XY` as a line is taken to
imply that `X` does not equal `Y` and that is included as a hypothesis, and a reference to `XY`
being parallel to something is considered a reference to it as a line. However, such an implicit
hypothesis about two points being different is included only once for any given two points (even
if it follows from more than one reference to a line or an angle), if `X ≠ Y` is included then
`Y ≠ X` is not included separately, and such hypotheses are not included in the case where there
is also a reference in the problem to a triangle including those two points, or to strict
betweenness of three points including those two. If betweenness is stated, it is taken to be
strict betweenness. However, segments and sides are taken to include their endpoints (unless
this makes a problem false), although those degenerate cases might not necessarily have been
considered when the problem was formulated and contestants might not have been expected to deal
with them. A reference to a point being on a side or a segment is expressed directly with `wbtw`
rather than more literally with `affine_segment`.
-/
library_note "IMO geometry formalization conventions"

noncomputable theory

open affine affine.simplex euclidean_geometry finite_dimensional
open_locale affine euclidean_geometry real

local attribute [instance] fact_finite_dimensional_of_finrank_eq_succ

variables (V : Type*) (Pt : Type*)
variables [normed_add_comm_group V] [inner_product_space ℝ V] [metric_space Pt]
variables [normed_add_torsor V Pt] [hd2 : fact (finrank ℝ V = 2)]
include hd2

/-- A configuration satisfying the conditions of the problem. We define this structure to avoid
passing many hypotheses around as we build up information about the configuration; the final
result for a statement of the problem not using this structure is then deduced from one in terms
of this structure. -/
@[nolint has_nonempty_instance]
structure imo2019q2_cfg :=
(A B C A₁ B₁ P Q P₁ Q₁ : Pt)
(affine_independent_ABC : affine_independent ℝ ![A, B, C])
(wbtw_B_A₁_C : wbtw ℝ B A₁ C)
(wbtw_A_B₁_C : wbtw ℝ A B₁ C)
(wbtw_A_P_A₁ : wbtw ℝ A P A₁)
(wbtw_B_Q_B₁ : wbtw ℝ B Q B₁)
(PQ_parallel_AB : line[ℝ, P, Q] ∥ line[ℝ, A, B])
-- A hypothesis implicit in the named line.
(P_ne_Q : P ≠ Q)
(sbtw_P_B₁_P₁ : sbtw ℝ P B₁ P₁)
(angle_PP₁C_eq_angle_BAC : ∠ P P₁ C = ∠ B A C)
-- A hypothesis implicit in the first named angle.
(C_ne_P₁ : C ≠ P₁)
(sbtw_Q_A₁_Q₁ : sbtw ℝ Q A₁ Q₁)
(angle_CQ₁Q_eq_angle_CBA : ∠ C Q₁ Q = ∠ C B A)
-- A hypothesis implicit in the first named angle.
(C_ne_Q₁ : C ≠ Q₁)

/-- A default choice of orientation, for lemmas that need to pick one. -/
def some_orientation : module.oriented ℝ V (fin 2) :=
⟨basis.orientation (fin_basis_of_finrank_eq _ _ hd2.out)⟩

variables {V Pt}

namespace imo2019q2_cfg

variables (cfg : imo2019q2_cfg V Pt)

/-- The configuration has symmetry, allowing results proved for one point to be applied for
another (where the informal solution says "similarly"). -/
def symm : imo2019q2_cfg V Pt :=
{ A := cfg.B,
  B := cfg.A,
  C := cfg.C,
  A₁ := cfg.B₁,
  B₁ := cfg.A₁,
  P := cfg.Q,
  Q := cfg.P,
  P₁ := cfg.Q₁,
  Q₁ := cfg.P₁,
  affine_independent_ABC := begin
    rw ←affine_independent_equiv (equiv.swap (0 : fin 3) 1),
    convert cfg.affine_independent_ABC using 1,
    ext x,
    fin_cases x;
      refl
  end,
  wbtw_B_A₁_C := cfg.wbtw_A_B₁_C,
  wbtw_A_B₁_C := cfg.wbtw_B_A₁_C,
  wbtw_A_P_A₁ := cfg.wbtw_B_Q_B₁,
  wbtw_B_Q_B₁ := cfg.wbtw_A_P_A₁,
  PQ_parallel_AB := set.pair_comm cfg.P cfg.Q ▸ set.pair_comm cfg.A cfg.B ▸ cfg.PQ_parallel_AB,
  P_ne_Q := cfg.P_ne_Q.symm,
  sbtw_P_B₁_P₁ := cfg.sbtw_Q_A₁_Q₁,
  angle_PP₁C_eq_angle_BAC :=
    angle_comm cfg.C cfg.Q₁ cfg.Q ▸ angle_comm cfg.C cfg.B cfg.A ▸ cfg.angle_CQ₁Q_eq_angle_CBA,
  C_ne_P₁ := cfg.C_ne_Q₁,
  sbtw_Q_A₁_Q₁ := cfg.sbtw_P_B₁_P₁,
  angle_CQ₁Q_eq_angle_CBA :=
    angle_comm cfg.P cfg.P₁ cfg.C ▸ angle_comm cfg.B cfg.A cfg.C ▸ cfg.angle_PP₁C_eq_angle_BAC,
  C_ne_Q₁ := cfg.C_ne_P₁ }

/-! ### Configuration properties that are obvious from the diagram, and construction of the
points `A₂` and `B₂` -/

lemma A_ne_B : cfg.A ≠ cfg.B := cfg.affine_independent_ABC.injective.ne
  (dec_trivial : (0 : fin 3) ≠ 1)

lemma A_ne_C : cfg.A ≠ cfg.C := cfg.affine_independent_ABC.injective.ne
  (dec_trivial : (0 : fin 3) ≠ 2)

lemma B_ne_C : cfg.B ≠ cfg.C := cfg.affine_independent_ABC.injective.ne
  (dec_trivial : (1 : fin 3) ≠ 2)

lemma not_collinear_ABC : ¬collinear ℝ ({cfg.A, cfg.B, cfg.C} : set Pt) :=
affine_independent_iff_not_collinear_set.1 cfg.affine_independent_ABC

/-- `ABC` as a `triangle`. -/
def triangle_ABC : triangle ℝ Pt := ⟨_, cfg.affine_independent_ABC⟩

lemma A_mem_circumsphere : cfg.A ∈ cfg.triangle_ABC.circumsphere :=
cfg.triangle_ABC.mem_circumsphere 0

lemma B_mem_circumsphere : cfg.B ∈ cfg.triangle_ABC.circumsphere :=
cfg.triangle_ABC.mem_circumsphere 1

lemma C_mem_circumsphere : cfg.C ∈ cfg.triangle_ABC.circumsphere :=
cfg.triangle_ABC.mem_circumsphere 2

lemma symm_triangle_ABC : cfg.symm.triangle_ABC = cfg.triangle_ABC.reindex (equiv.swap 0 1) :=
by { ext i, fin_cases i; refl }

lemma symm_triangle_ABC_circumsphere :
  cfg.symm.triangle_ABC.circumsphere = cfg.triangle_ABC.circumsphere :=
by rw [symm_triangle_ABC, affine.simplex.circumsphere_reindex]

/-- `A₂` is the second point of intersection of the ray `AA₁` with the circumcircle of `ABC`. -/
def A₂ : Pt := cfg.triangle_ABC.circumsphere.second_inter cfg.A (cfg.A₁ -ᵥ cfg.A)

/-- `B₂` is the second point of intersection of the ray `BB₁` with the circumcircle of `ABC`. -/
def B₂ : Pt := cfg.triangle_ABC.circumsphere.second_inter cfg.B (cfg.B₁ -ᵥ cfg.B)

lemma A₂_mem_circumsphere : cfg.A₂ ∈ cfg.triangle_ABC.circumsphere :=
(sphere.second_inter_mem _).2 cfg.A_mem_circumsphere

lemma B₂_mem_circumsphere : cfg.B₂ ∈ cfg.triangle_ABC.circumsphere :=
(sphere.second_inter_mem _).2 cfg.B_mem_circumsphere

lemma symm_A₂ : cfg.symm.A₂ = cfg.B₂ :=
by { simp_rw [A₂, B₂, symm_triangle_ABC_circumsphere], refl }

lemma QP_parallel_BA : line[ℝ, cfg.Q, cfg.P] ∥ line[ℝ, cfg.B, cfg.A] :=
by { rw [set.pair_comm cfg.Q, set.pair_comm cfg.B], exact cfg.PQ_parallel_AB }

lemma A_ne_A₁ : cfg.A ≠ cfg.A₁ :=
begin
  intro h,
  have h' := cfg.not_collinear_ABC,
  rw [h, set.insert_comm] at h',
  exact h' cfg.wbtw_B_A₁_C.collinear
end

lemma collinear_PAA₁A₂ : collinear ℝ ({cfg.P, cfg.A, cfg.A₁, cfg.A₂} : set Pt) :=
begin
  rw [A₂,
      (cfg.triangle_ABC.circumsphere.second_inter_collinear cfg.A cfg.A₁).collinear_insert_iff_of_ne
        (set.mem_insert _ _) (set.mem_insert_of_mem _ (set.mem_insert _ _)) cfg.A_ne_A₁,
      set.insert_comm],
  exact cfg.wbtw_A_P_A₁.collinear
end

lemma A₁_ne_C : cfg.A₁ ≠ cfg.C :=
begin
  intro h,
  have hsbtw := cfg.sbtw_Q_A₁_Q₁,
  rw h at hsbtw,
  have ha := hsbtw.angle₂₃₁_eq_zero,
  rw [angle_CQ₁Q_eq_angle_CBA, angle_comm] at ha,
  exact (angle_ne_zero_of_not_collinear cfg.not_collinear_ABC) ha
end

lemma B₁_ne_C : cfg.B₁ ≠ cfg.C := cfg.symm.A₁_ne_C

lemma Q_not_mem_CB : cfg.Q ∉ line[ℝ, cfg.C, cfg.B] :=
begin
  intro hQ,
  have hQA₁ : line[ℝ, cfg.Q, cfg.A₁] ≤ line[ℝ, cfg.C, cfg.B] :=
    affine_span_pair_le_of_mem_of_mem hQ cfg.wbtw_B_A₁_C.symm.mem_affine_span,
  have hQ₁ : cfg.Q₁ ∈ line[ℝ, cfg.C, cfg.B],
  { rw affine_subspace.le_def' at hQA₁,
    exact hQA₁ _ cfg.sbtw_Q_A₁_Q₁.right_mem_affine_span },
  have hc : collinear ℝ ({cfg.C, cfg.Q₁, cfg.Q} : set Pt),
  { have hc' : collinear ℝ ({cfg.B, cfg.C, cfg.Q₁, cfg.Q} : set Pt),
    { rw [set.insert_comm cfg.B, set.insert_comm cfg.B, set.pair_comm, set.insert_comm cfg.C,
          set.insert_comm cfg.C],
      exact collinear_insert_insert_of_mem_affine_span_pair hQ₁ hQ },
    exact hc'.subset (set.subset_insert _ _) },
  rw [collinear_iff_eq_or_eq_or_angle_eq_zero_or_angle_eq_pi, cfg.angle_CQ₁Q_eq_angle_CBA,
      or_iff_right cfg.C_ne_Q₁, or_iff_right cfg.sbtw_Q_A₁_Q₁.left_ne_right, angle_comm] at hc,
  exact cfg.not_collinear_ABC (hc.elim collinear_of_angle_eq_zero collinear_of_angle_eq_pi)
end

lemma Q_ne_B : cfg.Q ≠ cfg.B :=
begin
  intro h,
  have h' := cfg.Q_not_mem_CB,
  rw h at h',
  exact h' (right_mem_affine_span_pair _ _ _)
end

lemma s_opp_side_CB_Q_Q₁ : line[ℝ, cfg.C, cfg.B].s_opp_side cfg.Q cfg.Q₁ :=
cfg.sbtw_Q_A₁_Q₁.s_opp_side_of_not_mem_of_mem cfg.Q_not_mem_CB cfg.wbtw_B_A₁_C.symm.mem_affine_span

/-! ### Relate the orientations of different angles in the configuration -/

section oriented

variables [module.oriented ℝ V (fin 2)]

lemma oangle_CQ₁Q_sign_eq_oangle_CBA_sign :
  (∡ cfg.C cfg.Q₁ cfg.Q).sign = (∡ cfg.C cfg.B cfg.A).sign :=
by rw [←cfg.sbtw_Q_A₁_Q₁.symm.oangle_eq_right,
       cfg.s_opp_side_CB_Q_Q₁.oangle_sign_eq_neg (left_mem_affine_span_pair ℝ cfg.C cfg.B)
        cfg.wbtw_B_A₁_C.symm.mem_affine_span, ←real.angle.sign_neg, ←oangle_rev,
      cfg.wbtw_B_A₁_C.oangle_sign_eq_of_ne_right cfg.Q cfg.A₁_ne_C, oangle_rotate_sign,
      cfg.wbtw_B_Q_B₁.oangle_eq_right cfg.Q_ne_B,
      cfg.wbtw_A_B₁_C.symm.oangle_sign_eq_of_ne_left cfg.B cfg.B₁_ne_C.symm]

lemma oangle_CQ₁Q_eq_oangle_CBA : ∡ cfg.C cfg.Q₁ cfg.Q = ∡ cfg.C cfg.B cfg.A :=
oangle_eq_of_angle_eq_of_sign_eq cfg.angle_CQ₁Q_eq_angle_CBA cfg.oangle_CQ₁Q_sign_eq_oangle_CBA_sign

end oriented

/-! ### More obvious configuration properties -/

lemma A₁_ne_B : cfg.A₁ ≠ cfg.B :=
begin
  intro h,
  have hwbtw := cfg.wbtw_A_P_A₁,
  rw h at hwbtw,
  have hPQ : line[ℝ, cfg.P, cfg.Q] = line[ℝ, cfg.A, cfg.B],
  { rw affine_subspace.eq_iff_direction_eq_of_mem (left_mem_affine_span_pair _ _ _)
         hwbtw.mem_affine_span,
    exact cfg.PQ_parallel_AB.direction_eq },
  haveI := some_orientation V,
  have haQ : (2 : ℤ) • ∡ cfg.C cfg.B cfg.Q = (2 : ℤ) • ∡ cfg.C cfg.B cfg.A,
  { rw [collinear.two_zsmul_oangle_eq_right _ cfg.A_ne_B cfg.Q_ne_B],
    rw [set.pair_comm, set.insert_comm],
    refine collinear_insert_of_mem_affine_span_pair _,
    rw ←hPQ,
    exact right_mem_affine_span_pair _ _ _ },
  have ha : (2 : ℤ) • ∡ cfg.C cfg.B cfg.Q = (2 : ℤ) • ∡ cfg.C cfg.Q₁ cfg.Q,
  { rw [oangle_CQ₁Q_eq_oangle_CBA, haQ] },
  have hn : ¬collinear ℝ ({cfg.C, cfg.B, cfg.Q} : set Pt),
  { rw [collinear_iff_of_two_zsmul_oangle_eq haQ, set.pair_comm, set.insert_comm, set.pair_comm],
    exact cfg.not_collinear_ABC },
  have hc := cospherical_of_two_zsmul_oangle_eq_of_not_collinear ha hn,
  have hBQ₁ : cfg.B ≠ cfg.Q₁, { rw [←h], exact cfg.sbtw_Q_A₁_Q₁.ne_right },
  have hQQ₁ : cfg.Q ≠ cfg.Q₁ := cfg.sbtw_Q_A₁_Q₁.left_ne_right,
  have hBQ₁Q : affine_independent ℝ ![cfg.B, cfg.Q₁, cfg.Q] :=
    hc.affine_independent_of_mem_of_ne (set.mem_insert_of_mem _ (set.mem_insert _ _))
      (set.mem_insert_of_mem _ (set.mem_insert_of_mem _ (set.mem_insert _ _)))
      (set.mem_insert_of_mem _ (set.mem_insert_of_mem _ (set.mem_insert_of_mem _
        (set.mem_singleton _)))) hBQ₁ cfg.Q_ne_B.symm hQQ₁.symm,
  rw affine_independent_iff_not_collinear_set at hBQ₁Q,
  refine hBQ₁Q _,
  rw [←h, set.pair_comm, set.insert_comm],
  exact cfg.sbtw_Q_A₁_Q₁.wbtw.collinear
end

lemma sbtw_B_A₁_C : sbtw ℝ cfg.B cfg.A₁ cfg.C := ⟨cfg.wbtw_B_A₁_C, cfg.A₁_ne_B, cfg.A₁_ne_C⟩

lemma sbtw_A_B₁_C : sbtw ℝ cfg.A cfg.B₁ cfg.C := cfg.symm.sbtw_B_A₁_C

lemma sbtw_A_A₁_A₂ : sbtw ℝ cfg.A cfg.A₁ cfg.A₂ :=
begin
  refine sphere.sbtw_second_inter cfg.A_mem_circumsphere _,
  convert cfg.sbtw_B_A₁_C.dist_lt_max_dist _,
  change _ = max (dist (cfg.triangle_ABC.points 1) _) (dist (cfg.triangle_ABC.points 2) _),
  simp_rw [circumsphere_center, circumsphere_radius, dist_circumcenter_eq_circumradius, max_self]
end

lemma sbtw_B_B₁_B₂ : sbtw ℝ cfg.B cfg.B₁ cfg.B₂ :=
by { rw ←cfg.symm_A₂, exact cfg.symm.sbtw_A_A₁_A₂ }

lemma A₂_ne_A : cfg.A₂ ≠ cfg.A := cfg.sbtw_A_A₁_A₂.left_ne_right.symm

lemma A₂_ne_P : cfg.A₂ ≠ cfg.P := (cfg.sbtw_A_A₁_A₂.trans_wbtw_left_ne cfg.wbtw_A_P_A₁).symm

lemma A₂_ne_B : cfg.A₂ ≠ cfg.B :=
begin
  intro h,
  have h₁ := cfg.sbtw_A_A₁_A₂,
  rw h at h₁,
  refine cfg.not_collinear_ABC _,
  have hc : collinear ℝ ({cfg.A, cfg.C, cfg.B, cfg.A₁} : set Pt) :=
    collinear_insert_insert_of_mem_affine_span_pair h₁.left_mem_affine_span
      cfg.sbtw_B_A₁_C.right_mem_affine_span,
  refine hc.subset _,
  rw [set.pair_comm _ cfg.A₁, set.insert_comm _ cfg.A₁, set.insert_comm _ cfg.A₁, set.pair_comm],
  exact set.subset_insert _ _
end

lemma A₂_ne_C : cfg.A₂ ≠ cfg.C :=
begin
  intro h,
  have h₁ := cfg.sbtw_A_A₁_A₂,
  rw h at h₁,
  refine cfg.not_collinear_ABC _,
  have hc : collinear ℝ ({cfg.A, cfg.B, cfg.C, cfg.A₁} : set Pt) :=
    collinear_insert_insert_of_mem_affine_span_pair h₁.left_mem_affine_span
      cfg.sbtw_B_A₁_C.left_mem_affine_span,
  refine hc.subset (set.insert_subset_insert (set.insert_subset_insert _)),
  rw set.singleton_subset_iff,
  exact set.mem_insert _ _
end

lemma B₂_ne_B : cfg.B₂ ≠ cfg.B := by { rw ←symm_A₂, exact cfg.symm.A₂_ne_A }

lemma B₂_ne_Q : cfg.B₂ ≠ cfg.Q := by { rw ←symm_A₂, exact cfg.symm.A₂_ne_P }

lemma B₂_ne_A₂ : cfg.B₂ ≠ cfg.A₂ :=
begin
  intro h,
  have hA : sbtw ℝ (cfg.triangle_ABC.points 1) cfg.A₁ (cfg.triangle_ABC.points 2) :=
    cfg.sbtw_B_A₁_C,
  have hB : sbtw ℝ (cfg.triangle_ABC.points 0) cfg.B₁ (cfg.triangle_ABC.points 2) :=
    cfg.sbtw_A_B₁_C,
  have hA' : cfg.A₂ ∈ line[ℝ, cfg.triangle_ABC.points 0, cfg.A₁] :=
    sphere.second_inter_vsub_mem_affine_span _ _ _,
  have hB' : cfg.A₂ ∈ line[ℝ, cfg.triangle_ABC.points 1, cfg.B₁],
  { rw ←h, exact sphere.second_inter_vsub_mem_affine_span _ _ _ },
  exact (sbtw_of_sbtw_of_sbtw_of_mem_affine_span_pair dec_trivial hA hB hA' hB').symm.not_rotate
    cfg.sbtw_A_A₁_A₂.wbtw
end

lemma wbtw_B_Q_B₂ : wbtw ℝ cfg.B cfg.Q cfg.B₂ := cfg.sbtw_B_B₁_B₂.wbtw.trans_left cfg.wbtw_B_Q_B₁

/-! ### The first equality in the first angle chase in the solution -/

section oriented

variables [module.oriented ℝ V (fin 2)]

lemma two_zsmul_oangle_QPA₂_eq_two_zsmul_oangle_BAA₂ :
  (2 : ℤ) • ∡ cfg.Q cfg.P cfg.A₂ = (2 : ℤ) • ∡ cfg.B cfg.A cfg.A₂ :=
begin
  refine two_zsmul_oangle_of_parallel cfg.QP_parallel_BA _,
  convert affine_subspace.parallel.refl _ using 1,
  rw [cfg.collinear_PAA₁A₂.affine_span_eq_of_ne
        (set.mem_insert_of_mem _ (set.mem_insert_of_mem _ (set.mem_insert_of_mem _
          (set.mem_singleton _))))
        (set.mem_insert_of_mem _ (set.mem_insert _ _)) cfg.A₂_ne_A,
      cfg.collinear_PAA₁A₂.affine_span_eq_of_ne
        (set.mem_insert_of_mem _ (set.mem_insert_of_mem _ (set.mem_insert_of_mem _
          (set.mem_singleton _))))
        (set.mem_insert _ _) cfg.A₂_ne_P]
end

end oriented

/-! ### More obvious configuration properties -/

lemma not_collinear_QPA₂ : ¬ collinear ℝ ({cfg.Q, cfg.P, cfg.A₂} : set Pt) :=
begin
  haveI := some_orientation V,
  rw [collinear_iff_of_two_zsmul_oangle_eq cfg.two_zsmul_oangle_QPA₂_eq_two_zsmul_oangle_BAA₂,
      ←affine_independent_iff_not_collinear_set],
  have h : cospherical ({cfg.B, cfg.A, cfg.A₂} : set Pt),
  { refine cfg.triangle_ABC.circumsphere.cospherical.subset _,
    simp [set.insert_subset, cfg.A_mem_circumsphere, cfg.B_mem_circumsphere,
          cfg.A₂_mem_circumsphere] },
  exact h.affine_independent_of_ne cfg.A_ne_B.symm cfg.A₂_ne_B.symm cfg.A₂_ne_A.symm
end

lemma Q₁_ne_A₂ : cfg.Q₁ ≠ cfg.A₂ :=
begin
  intro h,
  have h₁ := cfg.sbtw_Q_A₁_Q₁,
  rw h at h₁,
  refine cfg.not_collinear_QPA₂ _,
  have hA₂ := cfg.sbtw_A_A₁_A₂.right_mem_affine_span,
  have hA₂A₁ : line[ℝ, cfg.A₂, cfg.A₁] ≤ line[ℝ, cfg.A, cfg.A₁] :=
    affine_span_pair_le_of_left_mem hA₂,
  have hQ : cfg.Q ∈ line[ℝ, cfg.A, cfg.A₁],
  { rw affine_subspace.le_def' at hA₂A₁,
    exact hA₂A₁ _ h₁.left_mem_affine_span },
  exact collinear_triple_of_mem_affine_span_pair hQ cfg.wbtw_A_P_A₁.mem_affine_span hA₂
end

lemma affine_independent_QPA₂ : affine_independent ℝ ![cfg.Q, cfg.P, cfg.A₂] :=
affine_independent_iff_not_collinear_set.2 cfg.not_collinear_QPA₂

lemma affine_independent_PQB₂ : affine_independent ℝ ![cfg.P, cfg.Q, cfg.B₂] :=
by { rw ←symm_A₂, exact cfg.symm.affine_independent_QPA₂ }

/-- `QPA₂` as a `triangle`. -/
def triangle_QPA₂ : triangle ℝ Pt := ⟨_, cfg.affine_independent_QPA₂⟩

/-- `PQB₂` as a `triangle`. -/
def triangle_PQB₂ : triangle ℝ Pt := ⟨_, cfg.affine_independent_PQB₂⟩

lemma symm_triangle_QPA₂ : cfg.symm.triangle_QPA₂ = cfg.triangle_PQB₂ :=
by { simp_rw [triangle_PQB₂, ←symm_A₂], ext i, fin_cases i; refl }

/-- `ω` is the circle containing `Q`, `P` and `A₂`, which will be shown also to contain `B₂`,
`P₁` and `Q₁`. -/
def ω : sphere Pt := cfg.triangle_QPA₂.circumsphere

lemma P_mem_ω : cfg.P ∈ cfg.ω := cfg.triangle_QPA₂.mem_circumsphere 1

lemma Q_mem_ω : cfg.Q ∈ cfg.ω := cfg.triangle_QPA₂.mem_circumsphere 0

/-! ### The rest of the first angle chase in the solution -/

section oriented

variables [module.oriented ℝ V (fin 2)]

lemma two_zsmul_oangle_QPA₂_eq_two_zsmul_oangle_QB₂A₂ :
  (2 : ℤ) • ∡ cfg.Q cfg.P cfg.A₂ = (2 : ℤ) • ∡ cfg.Q cfg.B₂ cfg.A₂ :=
calc (2 : ℤ) • ∡ cfg.Q cfg.P cfg.A₂ = (2 : ℤ) • ∡ cfg.B cfg.A cfg.A₂ :
    cfg.two_zsmul_oangle_QPA₂_eq_two_zsmul_oangle_BAA₂
  ... = (2 : ℤ) • ∡ cfg.B cfg.B₂ cfg.A₂ :
    sphere.two_zsmul_oangle_eq cfg.B_mem_circumsphere cfg.A_mem_circumsphere
      cfg.B₂_mem_circumsphere cfg.A₂_mem_circumsphere cfg.A_ne_B cfg.A₂_ne_A.symm
      cfg.B₂_ne_B cfg.B₂_ne_A₂
  ... = (2 : ℤ) • ∡ cfg.Q cfg.B₂ cfg.A₂ :
    by rw cfg.wbtw_B_Q_B₂.symm.oangle_eq_left cfg.B₂_ne_Q.symm

end oriented

/-! ### Conclusions from that first angle chase -/

lemma cospherical_QPB₂A₂ : cospherical ({cfg.Q, cfg.P, cfg.B₂, cfg.A₂} : set Pt) :=
begin
  haveI := some_orientation V,
  exact cospherical_of_two_zsmul_oangle_eq_of_not_collinear
    cfg.two_zsmul_oangle_QPA₂_eq_two_zsmul_oangle_QB₂A₂ cfg.not_collinear_QPA₂
end

lemma symm_ω_eq_triangle_PQB₂_circumsphere : cfg.symm.ω = cfg.triangle_PQB₂.circumsphere :=
by rw [ω, symm_triangle_QPA₂]

lemma symm_ω : cfg.symm.ω = cfg.ω :=
begin
  rw [symm_ω_eq_triangle_PQB₂_circumsphere, ω],
  refine circumsphere_eq_of_cospherical hd2.out cfg.cospherical_QPB₂A₂ _ _,
  { simp only [triangle_PQB₂, matrix.range_cons, matrix.range_empty, set.singleton_union,
               insert_emptyc_eq],
    rw set.insert_comm,
    refine set.insert_subset_insert (set.insert_subset_insert _),
    simp },
  { simp only [triangle_QPA₂, matrix.range_cons, matrix.range_empty, set.singleton_union,
               insert_emptyc_eq],
    refine set.insert_subset_insert (set.insert_subset_insert _),
    simp }
end

/-! ### The second angle chase in the solution -/

section oriented

variables [module.oriented ℝ V (fin 2)]

lemma two_zsmul_oangle_CA₂A₁_eq_two_zsmul_oangle_CBA :
  (2 : ℤ) • ∡ cfg.C cfg.A₂ cfg.A₁ = (2 : ℤ) • ∡ cfg.C cfg.B cfg.A :=
calc (2 : ℤ) • ∡ cfg.C cfg.A₂ cfg.A₁ = (2 : ℤ) • ∡ cfg.C cfg.A₂ cfg.A :
    by rw cfg.sbtw_A_A₁_A₂.symm.oangle_eq_right
  ... = (2 : ℤ) • ∡ cfg.C cfg.B cfg.A :
    sphere.two_zsmul_oangle_eq cfg.C_mem_circumsphere cfg.A₂_mem_circumsphere
      cfg.B_mem_circumsphere cfg.A_mem_circumsphere cfg.A₂_ne_C cfg.A₂_ne_A cfg.B_ne_C
      cfg.A_ne_B.symm

lemma two_zsmul_oangle_CA₂A₁_eq_two_zsmul_oangle_CQ₁A₁ :
  (2 : ℤ) • ∡ cfg.C cfg.A₂ cfg.A₁ = (2 : ℤ) • ∡ cfg.C cfg.Q₁ cfg.A₁ :=
calc (2 : ℤ) • ∡ cfg.C cfg.A₂ cfg.A₁ = (2 : ℤ) • ∡ cfg.C cfg.B cfg.A :
    cfg.two_zsmul_oangle_CA₂A₁_eq_two_zsmul_oangle_CBA
  ... = (2 : ℤ) • ∡ cfg.C cfg.Q₁ cfg.Q : by rw oangle_CQ₁Q_eq_oangle_CBA
  ... = (2 : ℤ) • ∡ cfg.C cfg.Q₁ cfg.A₁ : by rw cfg.sbtw_Q_A₁_Q₁.symm.oangle_eq_right

end oriented

/-! ### Conclusions from that second angle chase -/

lemma not_collinear_CA₂A₁ : ¬collinear ℝ ({cfg.C, cfg.A₂, cfg.A₁} : set Pt) :=
begin
  haveI := some_orientation V,
  rw [collinear_iff_of_two_zsmul_oangle_eq cfg.two_zsmul_oangle_CA₂A₁_eq_two_zsmul_oangle_CBA,
      set.pair_comm, set.insert_comm, set.pair_comm],
  exact cfg.not_collinear_ABC
end

lemma cospherical_A₁Q₁CA₂ : cospherical ({cfg.A₁, cfg.Q₁, cfg.C, cfg.A₂} : set Pt) :=
begin
  haveI := some_orientation V,
  rw [set.insert_comm cfg.Q₁, set.insert_comm cfg.A₁, set.pair_comm, set.insert_comm cfg.A₁,
      set.pair_comm],
  exact cospherical_of_two_zsmul_oangle_eq_of_not_collinear
    cfg.two_zsmul_oangle_CA₂A₁_eq_two_zsmul_oangle_CQ₁A₁ cfg.not_collinear_CA₂A₁
end

/-! ### The third angle chase in the solution -/

section oriented

variables [module.oriented ℝ V (fin 2)]

lemma two_zsmul_oangle_QQ₁A₂_eq_two_zsmul_oangle_QPA₂ :
  (2 : ℤ) • ∡ cfg.Q cfg.Q₁ cfg.A₂ = (2 : ℤ) • ∡ cfg.Q cfg.P cfg.A₂ :=
calc (2 : ℤ) • ∡ cfg.Q cfg.Q₁ cfg.A₂ = (2 : ℤ) • ∡ cfg.A₁ cfg.Q₁ cfg.A₂ :
    by rw cfg.sbtw_Q_A₁_Q₁.symm.oangle_eq_left
  ... = (2 : ℤ) • ∡ cfg.A₁ cfg.C cfg.A₂ :
    cfg.cospherical_A₁Q₁CA₂.two_zsmul_oangle_eq cfg.sbtw_Q_A₁_Q₁.right_ne cfg.Q₁_ne_A₂
      cfg.A₁_ne_C.symm cfg.A₂_ne_C.symm
  ... = (2 : ℤ) • ∡ cfg.B cfg.C cfg.A₂ : by rw cfg.sbtw_B_A₁_C.symm.oangle_eq_left
  ... = (2 : ℤ) • ∡ cfg.B cfg.A cfg.A₂ :
    sphere.two_zsmul_oangle_eq cfg.B_mem_circumsphere cfg.C_mem_circumsphere
      cfg.A_mem_circumsphere cfg.A₂_mem_circumsphere cfg.B_ne_C.symm cfg.A₂_ne_C.symm cfg.A_ne_B
      cfg.A₂_ne_A.symm
  ... = (2 : ℤ) • ∡ cfg.Q cfg.P cfg.A₂ : cfg.two_zsmul_oangle_QPA₂_eq_two_zsmul_oangle_BAA₂.symm

end oriented

/-! ### Conclusions from that third angle chase -/

lemma Q₁_mem_ω : cfg.Q₁ ∈ cfg.ω :=
begin
  haveI := some_orientation V,
  exact affine.triangle.mem_circumsphere_of_two_zsmul_oangle_eq (dec_trivial : (0 : fin 3) ≠ 1)
    (dec_trivial : (0 : fin 3) ≠ 2) dec_trivial cfg.two_zsmul_oangle_QQ₁A₂_eq_two_zsmul_oangle_QPA₂
end

lemma P₁_mem_ω : cfg.P₁ ∈ cfg.ω := by { rw ←symm_ω, exact cfg.symm.Q₁_mem_ω }

theorem result : concyclic ({cfg.P, cfg.Q, cfg.P₁, cfg.Q₁} : set Pt) :=
begin
  refine ⟨_, coplanar_of_fact_finrank_eq_two _⟩,
  rw cospherical_iff_exists_sphere,
  refine ⟨cfg.ω, _⟩,
  simp only [set.insert_subset, set.singleton_subset_iff],
  exact ⟨cfg.P_mem_ω, cfg.Q_mem_ω, cfg.P₁_mem_ω, cfg.Q₁_mem_ω⟩
end

end imo2019q2_cfg

theorem imo2019_q2 (A B C A₁ B₁ P Q P₁ Q₁ : Pt)
  (affine_independent_ABC : affine_independent ℝ ![A, B, C])
  (wbtw_B_A₁_C : wbtw ℝ B A₁ C) (wbtw_A_B₁_C : wbtw ℝ A B₁ C) (wbtw_A_P_A₁ : wbtw ℝ A P A₁)
  (wbtw_B_Q_B₁ : wbtw ℝ B Q B₁) (PQ_parallel_AB : line[ℝ, P, Q] ∥ line[ℝ, A, B]) (P_ne_Q : P ≠ Q)
  (sbtw_P_B₁_P₁ : sbtw ℝ P B₁ P₁) (angle_PP₁C_eq_angle_BAC : ∠ P P₁ C = ∠ B A C)
  (C_ne_P₁ : C ≠ P₁) (sbtw_Q_A₁_Q₁ : sbtw ℝ Q A₁ Q₁)
  (angle_CQ₁Q_eq_angle_CBA : ∠ C Q₁ Q = ∠ C B A) (C_ne_Q₁ : C ≠ Q₁) :
  concyclic ({P, Q, P₁, Q₁} : set Pt) :=
(⟨A, B, C, A₁, B₁, P, Q, P₁, Q₁, affine_independent_ABC, wbtw_B_A₁_C, wbtw_A_B₁_C, wbtw_A_P_A₁,
  wbtw_B_Q_B₁, PQ_parallel_AB, P_ne_Q, sbtw_P_B₁_P₁, angle_PP₁C_eq_angle_BAC, C_ne_P₁,
  sbtw_Q_A₁_Q₁, angle_CQ₁Q_eq_angle_CBA, C_ne_Q₁⟩ : imo2019q2_cfg V Pt).result


import algebra.module

universes u v w u₁ v₁

class is_ring_hom {α : Type u} {β : Type v} [comm_ring α] [comm_ring β] (f : α → β) : Prop :=
(map_add : ∀ {x y}, f (x + y) = f x + f y)
(map_mul : ∀ {x y}, f (x * y) = f x * f y)
(map_one : f 1 = 1)

namespace is_ring_hom

variables {α : Type u} {β : Type v} [comm_ring α] [comm_ring β]
variables (f : α → β) [is_ring_hom f] {x y : α}

lemma map_zero : f 0 = 0 :=
calc f 0 = f (0 + 0) - f 0 : by rw [map_add f]; simp
     ... = 0 : by simp

lemma map_neg : f (-x) = -f x :=
calc f (-x) = f (-x + x) - f x : by rw [map_add f]; simp
        ... = -f x : by simp [map_zero f]

lemma map_sub : f (x - y) = f x - f y :=
by simp [map_add f, map_neg f]

end is_ring_hom

section bilinear

variables {α : Type u} [comm_ring α]
include α

variables {β : Type v} {γ : Type w} {α₁ : Type u₁} {β₁ : Type v₁}
variables [module α β] [module α γ] [module α α₁] [module α β₁]

structure is_bilinear_map {β γ α₁}
  [module α β] [module α γ] [module α α₁]
  (f : β → γ → α₁) : Prop :=
(add_pair : ∀ x y z, f (x + y) z = f x z + f y z)
(pair_add : ∀ x y z, f x (y + z) = f x y + f x z)
(smul_pair : ∀ r x y, f (r • x) y = r • f x y)
(pair_smul : ∀ r x y, f x (r • y) = r • f x y)

variables {f : β → γ → α₁} (hf : is_bilinear_map f)
include hf

theorem is_bilinear_map.zero_pair : ∀ y, f 0 y = 0 :=
λ y, calc f 0 y
        = f (0 + 0) y - f 0 y : by rw [hf.add_pair 0 0 y]; simp
    ... = 0 : by simp

theorem is_bilinear_map.pair_zero : ∀ x, f x 0 = 0 :=
λ x, calc f x 0
        = f x (0 + 0) - f x 0 : by rw [hf.pair_add x 0 0]; simp
    ... = 0 : by simp

theorem is_bilinear_map.linear_pair (y : γ) : is_linear_map (λ x, f x y) :=
{ add  := λ m n, hf.add_pair m n y,
  smul := λ r m, hf.smul_pair r m y }

theorem is_bilinear_map.pair_linear (x : β) : is_linear_map (λ y, f x y) :=
{ add  := λ m n, hf.pair_add x m n,
  smul := λ r m, hf.pair_smul r x m }

theorem is_bilinear_map.smul_smul : ∀ r₁ r₂ x y, f (r₁ • x) (r₂ • y) = (r₁ * r₂) • f x y :=
λ r₁ r₂ x y, by rw [hf.smul_pair, hf.pair_smul, mul_smul]

variables {g : α₁ → β₁} (hg : is_linear_map g)
include hg

theorem is_bilinear_map.comp : is_bilinear_map (λ x y, g (f x y)) :=
{ add_pair  := λ x y z, by rw [hf.add_pair, hg.add],
  pair_add  := λ x y z, by rw [hf.pair_add, hg.add],
  smul_pair := λ r x y, by rw [hf.smul_pair, hg.smul],
  pair_smul := λ r x y, by rw [hf.pair_smul, hg.smul] }

end bilinear

class algebra (α : out_param $ Type u) [comm_ring α] (β : Type v) extends module α β, has_mul β :=
(mul_bilinear : is_bilinear_map $ @has_mul.mul β _)

class alternative_algebra (α : out_param $ Type u) [comm_ring α] (β : Type v) extends algebra α β :=
(mul_self_zero : ∀ x : β, x * x = 0)

class associative_algebra (α : out_param $ Type u) [comm_ring α] (β : Type v) extends algebra α β :=
(mul_assoc : ∀ x y z : β, (x * y) * z = x * (y * z))

class power_associative_algebra (α : out_param $ Type u) [comm_ring α] (β : Type v) extends algebra α β :=
(pow_assoc : ∀ x : β, (x * x) * x = x * (x * x))

class commutative_algebra (α : out_param $ Type u) [comm_ring α] (β : Type v) extends algebra α β :=
(mul_comm : ∀ x y : β, x * y = y * x)

class unitary_algebra (α : out_param $ Type u) [comm_ring α] (β : Type v) extends algebra α β, has_one β :=
(mul_one : ∀ x : β, x * 1 = x)
(one_mul : ∀ x : β, 1 * x = x)

class unitary_division_algebra (α : out_param $ Type u) [comm_ring α] (β : Type v) extends unitary_algebra α β :=
(zero_ne_one : (0:β) ≠ (1:β))
(left_inv : ∀ x y : β, ∃ z, z * x = y)
(right_inv : ∀ x y : β, ∃ z, x * z = y)

class star_algebra (α : out_param $ Type u) [comm_ring α] (β : Type v) extends unitary_division_algebra α β, has_inv β :=
(inv_inv : ∀ x : β, (x⁻¹)⁻¹ = x)
(mul_inv : ∀ x y : β, (x * y)⁻¹ = y⁻¹ * x⁻¹)

class lie_algebra (α : out_param $ Type u) [comm_ring α] (β : Type v) extends alternative_algebra α β :=
(jacobi : ∀ x y z : β, x * (y * z) + y * (z * x) + z * (x * y) = 0)

class unitary_associative_commutative_algebra (α : out_param $ Type u) [comm_ring α] (β : Type v) extends unitary_algebra α β :=
(mul_assoc : ∀ x y z : β, (x * y) * z = x * (y * z))
(mul_comm : ∀ x y : β, x * y = y * x)

instance unitary_associative_commutative_algebra.has_mul (α : out_param $ Type u) [comm_ring α] (β : Type v) [unitary_associative_commutative_algebra α β] : has_mul β :=
by apply_instance

def unitary_associative_commutative_algebra.of_is_ring_hom
  {α : Type u} [comm_ring α] {β : Type v} [comm_ring β]
  (f : α → β) [is_ring_hom f] : unitary_associative_commutative_algebra α β :=
{ smul := λ x y, f x * y,
  smul_add := λ r x y, by simp [mul_add],
  add_smul := λ r₁ r₂ x, by simp [is_ring_hom.map_add f, add_mul],
  mul_smul := λ r₁ r₂ x, by simp [is_ring_hom.map_mul f, mul_assoc],
  one_smul := λ x, by simp [is_ring_hom.map_one f],
  mul_bilinear :=
    { add_pair  := add_mul,
      pair_add  := mul_add,
      smul_pair := λ r x y, mul_assoc (f r) x y,
      pair_smul := λ r x y, mul_left_comm x (f r) y },
  .. _inst_2 }

namespace unitary_associative_commutative_algebra

variables {α : Type u} [comm_ring α]
variables {β : Type v} [unitary_associative_commutative_algebra α β]
include α

def to_comm_ring : comm_ring β :=
{ left_distrib  := (algebra.mul_bilinear β).pair_add,
  right_distrib := (algebra.mul_bilinear β).add_pair,
  ..module.to_add_comm_group β, .._inst_2 }

local attribute [instance] to_comm_ring

def to_is_ring_hom : is_ring_hom (λ x:α, (x • 1:β)) :=
{ map_add := λ x y, add_smul,
  map_mul := λ x y, (congr_arg _ (unitary_algebra.mul_one 1).symm).trans ((algebra.mul_bilinear β).smul_smul x y 1 1).symm,
  map_one := one_smul }

end unitary_associative_commutative_algebra

section lie_algebra

variables {α : Type u} [comm_ring α]
variables {β : Type v} [lie_algebra α β] (x y : β)

include α β

def lie_algebra.has_mul : has_mul β := by apply_instance

local attribute [instance] lie_algebra.has_mul

theorem lie_algebra.anti_commutative  : x * y = -(y * x) :=
have h1 : _ := alternative_algebra.mul_self_zero (x + y),
sorry

theorem lie_algebra.flexible  : (x * y) * x = x * (y * x) :=
have h1 : _ := @lie_algebra.jacobi α _inst_1 β _inst_2 x x y,
have h2 : _ := @lie_algebra.jacobi α _inst_1 β _inst_2 x y y,
begin end

end lie_algebra


import data.real.basic
import data.real.nnreal

theorem JBMO_Problem_1_2000 (x  y : nnreal) :
x^3 +y^3 +(x+y)^3 +30*x*y = 2000
→ x+y = 10 := sorry


import game.max.level01 -- hide

open_locale classical -- hide

noncomputable theory -- hide

namespace xena -- hide

/-
# Chapter 4 : Max and abs

## Level 2

`max_comm` is the statement that `max a b = max b a`. See if you can prove it.
-/

/- Hint : Hint
Again, do a case split with `cases le_total a b`.
-/

/- Lemma
For any real numbers $a$ and $b$, we have $\max(a,b) = \max(b,a).$
-/
theorem max_comm (a b : ℝ) : max a b = max b a :=
begin
  cases le_total a b with h h;
  rw max_eq_right h;
  rw max_eq_left h,

end

end xena --hide


-- import SciLean.Core.CoreFunctionProperties
import SciLean.Core.AdjDiff

namespace SciLean

--------------------------------------------------------------------------------
-- Variational Dual
--------------------------------------------------------------------------------

-- variational version of †
noncomputable
def variationalDual (F : (X⟿Y) → (LocIntDom X → ℝ)) : (X⟿Y) :=
  let has_dual := ∃ A : (X⟿Y) → (X⟿ℝ), HasAdjointT A ∧ ∀ ϕ, F ϕ = ∫ (A ϕ)
  match Classical.propDecidable (has_dual) with
  | isTrue h =>
    let A := Classical.choose h
    A† (λ _ ⟿ 1)
  | isFalse _ => 0

instance (F : (X⟿Y) → (LocIntDom X → ℝ))
  : Dagger F (variationalDual F) := ⟨⟩

-- variational version of ∇
noncomputable
def variationalGradient (F : (X⟿Y) → LocIntDom X → ℝ) (f : X⟿Y) : X ⟿ Y := (∂ F f)†

instance (F : (X⟿Y) → LocIntDom X → ℝ) : Nabla F (variationalGradient F) := ⟨⟩

-- Properties

instance integral.arg_f.isLin : IsLin (integral : (X⟿Y) → LocIntDom X → Y) := sorry_proof

-- @[simp ↓ low, diff low]
-- theorem variationalGradient_unfold (F : (X⟿Y) → LocIntDom X → ℝ)
--   : ∇ F = λ f => (∂ F f)† := by rfl

@[simp ↓, diff]
theorem varDual_smooth_fun (F : (X⟿Y) → (X⟿ℝ)) [HasAdjointT F]
  : (λ (f : X ⟿ Y) => ∫ (F f))† = F† (λ _ ⟿ 1) := sorry_proof

@[simp ↓, diff]
theorem variationalGradient_on_integral (F : (X⟿Y) → (X⟿ℝ)) [inst : HasAdjDiffT F]
  : ∇ f, ∫ (F f) = λ f => ∂† F f (λ _ ⟿ 1) :=
by
  have _ := inst.1.1
  have _ := inst.1.2
  unfold variationalGradient
  unfold adjointDifferential
  symdiff
  symdiff
  done

@[simp ↓, diff]
theorem varDual_smooth_fun_elemwise [Hilbert Y] (A : X → Y → ℝ) [∀ x, HasAdjointT (A x)] [IsSmoothNT 2 A]
  : (λ (g : X ⟿ Y) => ∫ x, A x (g x))† = (λ x ⟿ (A x)† 1) := sorry_proof

@[simp ↓, diff]
theorem varDual_smooth_fun_elemwise' [Hilbert Y] [Vec Z] (f : X → Z) [IsSmoothT f]
  (A : Y → Z → ℝ) [∀ z, HasAdjointT (λ y => A y z)] [IsSmoothNT 2 A]
  : (λ (g : X ⟿ Y) => ∫ x, A (g x) (f x))† = (λ x ⟿ (λ y => A y (f x))† 1) :=
by apply varDual_smooth_fun_elemwise (λ x y => A y (f x)); done


inductive bin : nat → Prop
| bin_epsilon : bin 0
| bin_0 : ∀ (n: nat), bin n → bin (2 * n)
| bin_1 : ∀ (n: nat), bin n → bin (2 * n + 1)

def is_expressible_in_binary_notation := bin

lemma two_gt_one_ : 2 > 1 :=
begin
    exact dec_trivial
end

lemma two_gt_zero : 2 > 0 :=
begin
    exact dec_trivial
end

example : ∀ (n : nat), is_expressible_in_binary_notation n :=
begin
    intros,
    unfold is_expressible_in_binary_notation,
    apply well_founded.induction nat.lt_wf n _; clear n,
    intros x ih,
    let p := x % 2,
    let q := x / 2,
    have: p = 0 ∨ p = 1,
        apply nat.mod_two_eq_zero_or_one,
    cases x,
        apply bin.bin_epsilon,
    have succ_x_gt_zero: nat.succ x > 0, from nat.zero_lt_succ x,
    have div_2_lt : nat.succ x / 2 < nat.succ x,
        from nat.div_lt_self succ_x_gt_zero two_gt_one_,
    cases this,
        have hoge : p + 2 * q = nat.succ x, from nat.mod_add_div (nat.succ x) 2,
        rw ←hoge,
        rw this,
        rw zero_add,
        apply bin.bin_0 q,
        apply ih q,
        assumption,
    have hoge : p + 2 * q = nat.succ x, from nat.mod_add_div (nat.succ x) 2,
    rw ←hoge,
    rw this,
    rw add_comm,
    apply bin.bin_1 q,
    apply ih q,
    assumption,
end


/-
Copyright (c) 2018 Chris Hughes. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Chris Hughes, Johannes Hölzl, Scott Morrison, Jens Wagemaker

! This file was ported from Lean 3 source module data.polynomial.field_division
! leanprover-community/mathlib commit bbeb185db4ccee8ed07dc48449414ebfa39cb821
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathlib.Data.Polynomial.Derivative
import Mathlib.Data.Polynomial.RingDivision
import Mathlib.RingTheory.EuclideanDomain

/-!
# Theory of univariate polynomials

This file starts looking like the ring theory of $ R[X] $

-/

noncomputable section

open Classical BigOperators Polynomial

namespace Polynomial

universe u v w y z

variable {R : Type u} {S : Type v} {k : Type y} {A : Type z} {a b : R} {n : ℕ}

section IsDomain

variable [CommRing R] [IsDomain R]

theorem derivative_rootMultiplicity_of_root [CharZero R] {p : R[X]} {t : R} (hpt : p.IsRoot t) :
    p.derivative.rootMultiplicity t = p.rootMultiplicity t - 1 := by
  rcases eq_or_ne p 0 with (rfl | hp)
  · simp
  nth_rw 1 [← p.divByMonic_mul_pow_rootMultiplicity_eq t]
  simp only [derivative_pow, derivative_mul, derivative_sub, derivative_X, derivative_C, sub_zero,
    mul_one]
  set n := p.rootMultiplicity t - 1
  have hn : n + 1 = _ := tsub_add_cancel_of_le ((rootMultiplicity_pos hp).mpr hpt)
  rw [← hn]
  set q := p /ₘ (X - C t) ^ (n + 1) with _hq
  convert_to rootMultiplicity t ((X - C t) ^ n * (derivative q * (X - C t) + q * C ↑(n + 1))) = n
  · congr
    rw [mul_add, mul_left_comm <| (X - C t) ^ n, ← pow_succ']
    congr 1
    rw [mul_left_comm <| (X - C t) ^ n, mul_comm <| (X - C t) ^ n]
  have h : eval t (derivative q * (X - C t) + q * C (R := R) ↑(n + 1)) ≠ 0 := by
    suffices eval t q * ↑(n + 1) ≠ 0 by simpa
    refine' mul_ne_zero _ (Nat.cast_ne_zero.mpr n.succ_ne_zero)
    convert eval_divByMonic_pow_rootMultiplicity_ne_zero t hp
  rw [rootMultiplicity_mul, rootMultiplicity_X_sub_C_pow, rootMultiplicity_eq_zero h, add_zero]
  refine' mul_ne_zero (pow_ne_zero n <| X_sub_C_ne_zero t) _
  contrapose! h
  rw [h, eval_zero]
#align polynomial.derivative_root_multiplicity_of_root Polynomial.derivative_rootMultiplicity_of_root

theorem rootMultiplicity_sub_one_le_derivative_rootMultiplicity [CharZero R] (p : R[X]) (t : R) :
    p.rootMultiplicity t - 1 ≤ p.derivative.rootMultiplicity t := by
  by_cases p.IsRoot t
  · exact (derivative_rootMultiplicity_of_root h).symm.le
  · rw [rootMultiplicity_eq_zero h, zero_tsub]
    exact zero_le _
#align polynomial.root_multiplicity_sub_one_le_derivative_root_multiplicity Polynomial.rootMultiplicity_sub_one_le_derivative_rootMultiplicity

section NormalizationMonoid

variable [NormalizationMonoid R]

instance : NormalizationMonoid R[X] where
  normUnit p :=
    ⟨C ↑(normUnit p.leadingCoeff), C ↑(normUnit p.leadingCoeff)⁻¹, by
      rw [← RingHom.map_mul, Units.mul_inv, C_1], by rw [← RingHom.map_mul, Units.inv_mul, C_1]⟩
  normUnit_zero := Units.ext (by simp)
  normUnit_mul hp0 hq0 :=
    Units.ext
      (by
        dsimp
        rw [Ne.def, ← leadingCoeff_eq_zero] at *
        rw [leadingCoeff_mul, normUnit_mul hp0 hq0, Units.val_mul, C_mul])
  normUnit_coe_units u :=
    Units.ext
      (by
        dsimp
        rw [← mul_one u⁻¹, Units.val_mul, Units.eq_inv_mul_iff_mul_eq]
        rcases Polynomial.isUnit_iff.1 ⟨u, rfl⟩ with ⟨_, ⟨w, rfl⟩, h2⟩
        rw [← h2, leadingCoeff_C, normUnit_coe_units, ← C_mul, Units.mul_inv, C_1]
        rfl)

@[simp]
theorem coe_normUnit {p : R[X]} : (normUnit p : R[X]) = C ↑(normUnit p.leadingCoeff) := by
  simp [normUnit]
#align polynomial.coe_norm_unit Polynomial.coe_normUnit

theorem leadingCoeff_normalize (p : R[X]) :
    leadingCoeff (normalize p) = normalize (leadingCoeff p) := by simp
#align polynomial.leading_coeff_normalize Polynomial.leadingCoeff_normalize

theorem Monic.normalize_eq_self {p : R[X]} (hp : p.Monic) : normalize p = p := by
  simp only [Polynomial.coe_normUnit, normalize_apply, hp.leadingCoeff, normUnit_one,
    Units.val_one, Polynomial.C.map_one, mul_one]
#align polynomial.monic.normalize_eq_self Polynomial.Monic.normalize_eq_self

theorem roots_normalize {p : R[X]} : (normalize p).roots = p.roots := by
  rw [normalize_apply, mul_comm, coe_normUnit, roots_C_mul _ (normUnit (leadingCoeff p)).ne_zero]
#align polynomial.roots_normalize Polynomial.roots_normalize

end NormalizationMonoid

end IsDomain

section DivisionRing

variable [DivisionRing R] {p q : R[X]}

theorem degree_pos_of_ne_zero_of_nonunit (hp0 : p ≠ 0) (hp : ¬IsUnit p) : 0 < degree p :=
  lt_of_not_ge fun h => by
    rw [eq_C_of_degree_le_zero h] at hp0 hp
    exact hp (IsUnit.map C (IsUnit.mk0 (coeff p 0) (mt C_inj.2 (by simpa using hp0))))
#align polynomial.degree_pos_of_ne_zero_of_nonunit Polynomial.degree_pos_of_ne_zero_of_nonunit

theorem monic_mul_leadingCoeff_inv (h : p ≠ 0) : Monic (p * C (leadingCoeff p)⁻¹) := by
  rw [Monic, leadingCoeff_mul, leadingCoeff_C,
    mul_inv_cancel (show leadingCoeff p ≠ 0 from mt leadingCoeff_eq_zero.1 h)]
#align polynomial.monic_mul_leading_coeff_inv Polynomial.monic_mul_leadingCoeff_inv

theorem degree_mul_leadingCoeff_inv (p : R[X]) (h : q ≠ 0) :
    degree (p * C (leadingCoeff q)⁻¹) = degree p := by
  have h₁ : (leadingCoeff q)⁻¹ ≠ 0 := inv_ne_zero (mt leadingCoeff_eq_zero.1 h)
  rw [degree_mul, degree_C h₁, add_zero]
#align polynomial.degree_mul_leading_coeff_inv Polynomial.degree_mul_leadingCoeff_inv

@[simp]
theorem map_eq_zero [Semiring S] [Nontrivial S] (f : R →+* S) : p.map f = 0 ↔ p = 0 := by
  simp only [Polynomial.ext_iff]
  congr!
  simp [map_eq_zero, coeff_map, coeff_zero]
#align polynomial.map_eq_zero Polynomial.map_eq_zero

theorem map_ne_zero [Semiring S] [Nontrivial S] {f : R →+* S} (hp : p ≠ 0) : p.map f ≠ 0 :=
  mt (map_eq_zero f).1 hp
#align polynomial.map_ne_zero Polynomial.map_ne_zero

end DivisionRing

section Field

variable [Field R] {p q : R[X]}

theorem isUnit_iff_degree_eq_zero : IsUnit p ↔ degree p = 0 :=
  ⟨degree_eq_zero_of_isUnit, fun h =>
    have : degree p ≤ 0 := by simp [*, le_refl]
    have hc : coeff p 0 ≠ 0 := fun hc => by
      rw [eq_C_of_degree_le_zero this, hc] at h; simp at h
    isUnit_iff_dvd_one.2
      ⟨C (coeff p 0)⁻¹, by
        conv in p => rw [eq_C_of_degree_le_zero this]
        rw [← C_mul, _root_.mul_inv_cancel hc, C_1]⟩⟩
#align polynomial.is_unit_iff_degree_eq_zero Polynomial.isUnit_iff_degree_eq_zero

/-- Division of polynomials. See `polynomial.divByMonic` for more details.-/
def div (p q : R[X]) :=
  C (leadingCoeff q)⁻¹ * (p /ₘ (q * C (leadingCoeff q)⁻¹))
#align polynomial.div Polynomial.div

/-- Remainder of polynomial division. See `polynomial.modByMonic` for more details. -/
def mod (p q : R[X]) :=
  p %ₘ (q * C (leadingCoeff q)⁻¹)
#align polynomial.mod Polynomial.mod

private theorem quotient_mul_add_remainder_eq_aux (p q : R[X]) : q * div p q + mod p q = p :=
  if h : q = 0 then by simp only [h, MulZeroClass.zero_mul, mod, modByMonic_zero, zero_add]
  else
    by
    conv =>
      rhs
      rw [← modByMonic_add_div p (monic_mul_leadingCoeff_inv h)]
    rw [div, mod, add_comm, mul_assoc]

private theorem remainder_lt_aux (p : R[X]) (hq : q ≠ 0) : degree (mod p q) < degree q := by
  rw [← degree_mul_leadingCoeff_inv q hq];
    exact degree_modByMonic_lt p (monic_mul_leadingCoeff_inv hq)

instance : Div R[X] :=
  ⟨div⟩

instance : Mod R[X] :=
  ⟨mod⟩

theorem div_def : p / q = C (leadingCoeff q)⁻¹ * (p /ₘ (q * C (leadingCoeff q)⁻¹)) :=
  rfl
#align polynomial.div_def Polynomial.div_def

theorem mod_def : p % q = p %ₘ (q * C (leadingCoeff q)⁻¹) := rfl
#align polynomial.mod_def Polynomial.mod_def

theorem modByMonic_eq_mod (p : R[X]) (hq : Monic q) : p %ₘ q = p % q :=
  show p %ₘ q = p %ₘ (q * C (leadingCoeff q)⁻¹) by simp only [Monic.def.1 hq, inv_one, mul_one, C_1]
#align polynomial.mod_by_monic_eq_mod Polynomial.modByMonic_eq_mod

theorem divByMonic_eq_div (p : R[X]) (hq : Monic q) : p /ₘ q = p / q :=
  show p /ₘ q = C (leadingCoeff q)⁻¹ * (p /ₘ (q * C (leadingCoeff q)⁻¹)) by
    simp only [Monic.def.1 hq, inv_one, C_1, one_mul, mul_one]
#align polynomial.div_by_monic_eq_div Polynomial.divByMonic_eq_div

theorem mod_x_sub_c_eq_c_eval (p : R[X]) (a : R) : p % (X - C a) = C (p.eval a) :=
  modByMonic_eq_mod p (monic_X_sub_C a) ▸ modByMonic_X_sub_C_eq_C_eval _ _
set_option linter.uppercaseLean3 false in
#align polynomial.mod_X_sub_C_eq_C_eval Polynomial.mod_x_sub_c_eq_c_eval

theorem mul_div_eq_iff_isRoot : (X - C a) * (p / (X - C a)) = p ↔ IsRoot p a :=
  divByMonic_eq_div p (monic_X_sub_C a) ▸ mul_divByMonic_eq_iff_isRoot
#align polynomial.mul_div_eq_iff_is_root Polynomial.mul_div_eq_iff_isRoot

instance : EuclideanDomain R[X] :=
  { Polynomial.commRing,
    Polynomial.nontrivial with
    quotient := (· / ·)
    quotient_zero := by simp [div_def]
    remainder := (· % ·)
    r := _
    r_wellFounded := degree_lt_wf
    quotient_mul_add_remainder_eq := quotient_mul_add_remainder_eq_aux
    remainder_lt := fun p q hq => remainder_lt_aux _ hq
    mul_left_not_lt := fun p q hq => not_lt_of_ge (degree_le_mul_left _ hq) }

theorem mod_eq_self_iff (hq0 : q ≠ 0) : p % q = p ↔ degree p < degree q :=
  ⟨fun h => h ▸ EuclideanDomain.mod_lt _ hq0, fun h =>
    by
    have : ¬degree (q * C (leadingCoeff q)⁻¹) ≤ degree p :=
      not_le_of_gt <| by rwa [degree_mul_leadingCoeff_inv q hq0]
    rw [mod_def, modByMonic, dif_pos (monic_mul_leadingCoeff_inv hq0)]
    unfold divModByMonicAux
    dsimp
    simp only [this, false_and_iff, if_false]⟩
#align polynomial.mod_eq_self_iff Polynomial.mod_eq_self_iff

theorem div_eq_zero_iff (hq0 : q ≠ 0) : p / q = 0 ↔ degree p < degree q :=
  ⟨fun h => by
    have := EuclideanDomain.div_add_mod p q;
      rwa [h, MulZeroClass.mul_zero, zero_add, mod_eq_self_iff hq0] at this,
    fun h =>
    by
    have hlt : degree p < degree (q * C (leadingCoeff q)⁻¹) := by
      rwa [degree_mul_leadingCoeff_inv q hq0]
    have hm : Monic (q * C (leadingCoeff q)⁻¹) := monic_mul_leadingCoeff_inv hq0
    rw [div_def, (divByMonic_eq_zero_iff hm).2 hlt, MulZeroClass.mul_zero]⟩
#align polynomial.div_eq_zero_iff Polynomial.div_eq_zero_iff

theorem degree_add_div (hq0 : q ≠ 0) (hpq : degree q ≤ degree p) :
    degree q + degree (p / q) = degree p := by
  have : degree (p % q) < degree (q * (p / q)) :=
    calc
      degree (p % q) < degree q := EuclideanDomain.mod_lt _ hq0
      _ ≤ _ := degree_le_mul_left _ (mt (div_eq_zero_iff hq0).1 (not_lt_of_ge hpq))

  conv_rhs =>
    rw [← EuclideanDomain.div_add_mod p q, degree_add_eq_left_of_degree_lt this, degree_mul]
#align polynomial.degree_add_div Polynomial.degree_add_div

theorem degree_div_le (p q : R[X]) : degree (p / q) ≤ degree p :=
  if hq : q = 0 then by simp [hq]
  else by
    rw [div_def, mul_comm, degree_mul_leadingCoeff_inv _ hq]; exact degree_divByMonic_le _ _
#align polynomial.degree_div_le Polynomial.degree_div_le

theorem degree_div_lt (hp : p ≠ 0) (hq : 0 < degree q) : degree (p / q) < degree p := by
  have hq0 : q ≠ 0 := fun hq0 => by simp [hq0] at hq
  rw [div_def, mul_comm, degree_mul_leadingCoeff_inv _ hq0];
    exact
      degree_divByMonic_lt _ (monic_mul_leadingCoeff_inv hq0) hp
        (by rw [degree_mul_leadingCoeff_inv _ hq0]; exact hq)
#align polynomial.degree_div_lt Polynomial.degree_div_lt

@[simp]
theorem degree_map [DivisionRing k] (p : R[X]) (f : R →+* k) : degree (p.map f) = degree p :=
  p.degree_map_eq_of_injective f.injective
#align polynomial.degree_map Polynomial.degree_map

@[simp]
theorem natDegree_map [DivisionRing k] (f : R →+* k) : natDegree (p.map f) = natDegree p :=
  natDegree_eq_of_degree_eq (degree_map _ f)
#align polynomial.nat_degree_map Polynomial.natDegree_map

@[simp]
theorem leadingCoeff_map [DivisionRing k] (f : R →+* k) :
    leadingCoeff (p.map f) = f (leadingCoeff p) := by
  simp only [← coeff_natDegree, coeff_map f, natDegree_map]
#align polynomial.leading_coeff_map Polynomial.leadingCoeff_map

theorem monic_map_iff [DivisionRing k] {f : R →+* k} {p : R[X]} : (p.map f).Monic ↔ p.Monic := by
  rw [Monic, leadingCoeff_map, ← f.map_one, Function.Injective.eq_iff f.injective, Monic]
#align polynomial.monic_map_iff Polynomial.monic_map_iff

theorem map_div [Field k] (f : R →+* k) : (p / q).map f = p.map f / q.map f := by
  if hq0 : q = 0 then simp [hq0]
  else
    rw [div_def, div_def, Polynomial.map_mul, map_divByMonic f (monic_mul_leadingCoeff_inv hq0),
      Polynomial.map_mul, map_C, leadingCoeff_map, map_inv₀]
#align polynomial.map_div Polynomial.map_div

theorem map_mod [Field k] (f : R →+* k) : (p % q).map f = p.map f % q.map f :=
  if hq0 : q = 0 then by simp [hq0]
  else by
    rw [mod_def, mod_def, leadingCoeff_map f, ← map_inv₀ f, ← map_C f, ← Polynomial.map_mul f,
      map_modByMonic f (monic_mul_leadingCoeff_inv hq0)]
#align polynomial.map_mod Polynomial.map_mod

section

open EuclideanDomain

theorem gcd_map [Field k] (f : R →+* k) : gcd (p.map f) (q.map f) = (gcd p q).map f :=
  GCD.induction p q (fun x => by simp_rw [Polynomial.map_zero, EuclideanDomain.gcd_zero_left])
    fun x y _ ih => by rw [gcd_val, ← map_mod, ih, ← gcd_val]
#align polynomial.gcd_map Polynomial.gcd_map

end

theorem eval₂_gcd_eq_zero [CommSemiring k] {ϕ : R →+* k} {f g : R[X]} {α : k} (hf : f.eval₂ ϕ α = 0)
    (hg : g.eval₂ ϕ α = 0) : (EuclideanDomain.gcd f g).eval₂ ϕ α = 0 := by
  rw [EuclideanDomain.gcd_eq_gcd_ab f g, Polynomial.eval₂_add, Polynomial.eval₂_mul,
    Polynomial.eval₂_mul, hf, hg, MulZeroClass.zero_mul, MulZeroClass.zero_mul, zero_add]
#align polynomial.eval₂_gcd_eq_zero Polynomial.eval₂_gcd_eq_zero

theorem eval_gcd_eq_zero {f g : R[X]} {α : R} (hf : f.eval α = 0) (hg : g.eval α = 0) :
    (EuclideanDomain.gcd f g).eval α = 0 :=
  eval₂_gcd_eq_zero hf hg
#align polynomial.eval_gcd_eq_zero Polynomial.eval_gcd_eq_zero

theorem root_left_of_root_gcd [CommSemiring k] {ϕ : R →+* k} {f g : R[X]} {α : k}
    (hα : (EuclideanDomain.gcd f g).eval₂ ϕ α = 0) : f.eval₂ ϕ α = 0 := by
  cases' EuclideanDomain.gcd_dvd_left f g with p hp
  rw [hp, Polynomial.eval₂_mul, hα, MulZeroClass.zero_mul]
#align polynomial.root_left_of_root_gcd Polynomial.root_left_of_root_gcd

theorem root_right_of_root_gcd [CommSemiring k] {ϕ : R →+* k} {f g : R[X]} {α : k}
    (hα : (EuclideanDomain.gcd f g).eval₂ ϕ α = 0) : g.eval₂ ϕ α = 0 := by
  cases' EuclideanDomain.gcd_dvd_right f g with p hp
  rw [hp, Polynomial.eval₂_mul, hα, MulZeroClass.zero_mul]
#align polynomial.root_right_of_root_gcd Polynomial.root_right_of_root_gcd

theorem root_gcd_iff_root_left_right [CommSemiring k] {ϕ : R →+* k} {f g : R[X]} {α : k} :
    (EuclideanDomain.gcd f g).eval₂ ϕ α = 0 ↔ f.eval₂ ϕ α = 0 ∧ g.eval₂ ϕ α = 0 :=
  ⟨fun h => ⟨root_left_of_root_gcd h, root_right_of_root_gcd h⟩, fun h => eval₂_gcd_eq_zero h.1 h.2⟩
#align polynomial.root_gcd_iff_root_left_right Polynomial.root_gcd_iff_root_left_right

theorem isRoot_gcd_iff_isRoot_left_right {f g : R[X]} {α : R} :
    (EuclideanDomain.gcd f g).IsRoot α ↔ f.IsRoot α ∧ g.IsRoot α :=
  root_gcd_iff_root_left_right
#align polynomial.is_root_gcd_iff_is_root_left_right Polynomial.isRoot_gcd_iff_isRoot_left_right

theorem isCoprime_map [Field k] (f : R →+* k) : IsCoprime (p.map f) (q.map f) ↔ IsCoprime p q := by
  rw [← EuclideanDomain.gcd_isUnit_iff, ← EuclideanDomain.gcd_isUnit_iff, gcd_map, isUnit_map]
#align polynomial.is_coprime_map Polynomial.isCoprime_map

theorem mem_roots_map [CommRing k] [IsDomain k] {f : R →+* k} {x : k} (hp : p ≠ 0) :
    x ∈ (p.map f).roots ↔ p.eval₂ f x = 0 := by
  rw [mem_roots (map_ne_zero hp), IsRoot, Polynomial.eval_map]
#align polynomial.mem_roots_map Polynomial.mem_roots_map

-- Porting note: previously could not synthesize Algebra R S
set_option synthInstance.etaExperiment true in
theorem rootSet_monomial [CommRing S] [IsDomain S] [Algebra R S] {n : ℕ} (hn : n ≠ 0) {a : R}
    (ha : a ≠ 0) : (monomial n a).rootSet S = {0} := by
  rw [rootSet, map_monomial, roots_monomial ((_root_.map_ne_zero (algebraMap R S)).2 ha),
    Multiset.toFinset_nsmul _ _ hn, Multiset.toFinset_singleton, Finset.coe_singleton]
#align polynomial.root_set_monomial Polynomial.rootSet_monomial

-- Porting note: previously could not synthesize Algebra R S
set_option synthInstance.etaExperiment true in
theorem rootSet_C_mul_X_pow [CommRing S] [IsDomain S] [Algebra R S] {n : ℕ} (hn : n ≠ 0) {a : R}
    (ha : a ≠ 0) : rootSet (C a * X ^ n) S = {0} := by
  rw [C_mul_X_pow_eq_monomial, rootSet_monomial hn ha]
set_option linter.uppercaseLean3 false in
#align polynomial.root_set_C_mul_X_pow Polynomial.rootSet_C_mul_X_pow

-- Porting note: previously could not synthesize Algebra R S
set_option synthInstance.etaExperiment true in
theorem rootSet_X_pow [CommRing S] [IsDomain S] [Algebra R S] {n : ℕ} (hn : n ≠ 0) :
    (X ^ n : R[X]).rootSet S = {0} := by
  rw [← one_mul (X ^ n : R[X]), ← C_1, rootSet_C_mul_X_pow hn]
  exact one_ne_zero
set_option linter.uppercaseLean3 false in
#align polynomial.root_set_X_pow Polynomial.rootSet_X_pow

-- Porting note: previously could not synthesize Algebra R S
set_option synthInstance.etaExperiment true in
theorem rootSet_prod [CommRing S] [IsDomain S] [Algebra R S] {ι : Type _} (f : ι → R[X])
    (s : Finset ι) (h : s.prod f ≠ 0) : (s.prod f).rootSet S = ⋃ i ∈ s, (f i).rootSet S := by
  simp only [rootSet, ← Finset.mem_coe]
  rw [Polynomial.map_prod, roots_prod, Finset.bind_toFinset, s.val_toFinset, Finset.coe_bunionᵢ]
  rwa [← Polynomial.map_prod, Ne, map_eq_zero]
#align polynomial.root_set_prod Polynomial.rootSet_prod

theorem exists_root_of_degree_eq_one (h : degree p = 1) : ∃ x, IsRoot p x :=
  ⟨-(p.coeff 0 / p.coeff 1),
    by
    have : p.coeff 1 ≠ 0 := by
      have h' := natDegree_eq_of_degree_eq_some h
      change natDegree p = 1 at h'; rw [←h']
      exact mt leadingCoeff_eq_zero.1 fun h0 => by simp [h0] at h
    conv in p => rw [eq_X_add_C_of_degree_le_one (show degree p ≤ 1 by rw [h])]
    simp [IsRoot, mul_div_cancel' _ this]⟩
#align polynomial.exists_root_of_degree_eq_one Polynomial.exists_root_of_degree_eq_one

theorem coeff_inv_units (u : R[X]ˣ) (n : ℕ) : ((↑u : R[X]).coeff n)⁻¹ = (↑u⁻¹ : R[X]).coeff n := by
  rw [eq_C_of_degree_eq_zero (degree_coe_units u), eq_C_of_degree_eq_zero (degree_coe_units u⁻¹),
    coeff_C, coeff_C, inv_eq_one_div]
  split_ifs
  ·
    rw [div_eq_iff_mul_eq (coeff_coe_units_zero_ne_zero u), coeff_zero_eq_eval_zero,
        coeff_zero_eq_eval_zero, ← eval_mul, ← Units.val_mul, inv_mul_self];
      simp
  · simp
#align polynomial.coeff_inv_units Polynomial.coeff_inv_units

-- Porting note: previously could not synthesize NormalisationMonoid R[X]
set_option synthInstance.etaExperiment true in
theorem monic_normalize (hp0 : p ≠ 0) : Monic (normalize p) := by
  rw [Ne.def, ← leadingCoeff_eq_zero, ← Ne.def, ← isUnit_iff_ne_zero] at hp0
  rw [Monic, leadingCoeff_normalize, normalize_eq_one]
  apply hp0
#align polynomial.monic_normalize Polynomial.monic_normalize

theorem leadingCoeff_div (hpq : q.degree ≤ p.degree) :
    (p / q).leadingCoeff = p.leadingCoeff / q.leadingCoeff := by
  by_cases hq : q = 0; · simp [hq]
  rw [div_def, leadingCoeff_mul, leadingCoeff_C,
    leadingCoeff_divByMonic_of_monic (monic_mul_leadingCoeff_inv hq) _, mul_comm,
    div_eq_mul_inv]
  rwa [degree_mul_leadingCoeff_inv q hq]
#align polynomial.leading_coeff_div Polynomial.leadingCoeff_div

theorem div_C_mul : p / (C a * q) = C a⁻¹ * (p / q) := by
  by_cases ha : a = 0
  · simp [ha]
  simp only [div_def, leadingCoeff_mul, mul_inv, leadingCoeff_C, C.map_mul, mul_assoc]
  congr 3
  rw [mul_left_comm q, ← mul_assoc, ← C.map_mul, mul_inv_cancel ha, C.map_one, one_mul]
set_option linter.uppercaseLean3 false in
#align polynomial.div_C_mul Polynomial.div_C_mul

theorem C_mul_dvd (ha : a ≠ 0) : C a * p ∣ q ↔ p ∣ q :=
  ⟨fun h => dvd_trans (dvd_mul_left _ _) h, fun ⟨r, hr⟩ =>
    ⟨C a⁻¹ * r, by
      rw [mul_assoc, mul_left_comm p, ← mul_assoc, ← C.map_mul, _root_.mul_inv_cancel ha, C.map_one,
        one_mul, hr]⟩⟩
set_option linter.uppercaseLean3 false in
#align polynomial.C_mul_dvd Polynomial.C_mul_dvd

theorem dvd_C_mul (ha : a ≠ 0) : p ∣ Polynomial.C a * q ↔ p ∣ q :=
  ⟨fun ⟨r, hr⟩ =>
    ⟨C a⁻¹ * r, by
      rw [mul_left_comm p, ← hr, ← mul_assoc, ← C.map_mul, _root_.inv_mul_cancel ha, C.map_one,
        one_mul]⟩,
    fun h => dvd_trans h (dvd_mul_left _ _)⟩
set_option linter.uppercaseLean3 false in
#align polynomial.dvd_C_mul Polynomial.dvd_C_mul

-- Porting note: previously could not synthesize NormalisationMonoid R[X]
set_option synthInstance.etaExperiment true in
theorem coe_normUnit_of_ne_zero (hp : p ≠ 0) : (normUnit p : R[X]) = C p.leadingCoeff⁻¹ := by
  have : p.leadingCoeff ≠ 0 := mt leadingCoeff_eq_zero.mp hp
  simp [CommGroupWithZero.coe_normUnit _ this]
#align polynomial.coe_norm_unit_of_ne_zero Polynomial.coe_normUnit_of_ne_zero

-- Porting note: previously could not synthesize NormalisationMonoid R[X]
set_option synthInstance.etaExperiment true in
theorem normalize_monic (h : Monic p) : normalize p = p := by simp [h]
#align polynomial.normalize_monic Polynomial.normalize_monic

-- Porting note: previously could not synthesize NormalisationMonoid R[X]
set_option synthInstance.etaExperiment true in
theorem map_dvd_map' [Field k] (f : R →+* k) {x y : R[X]} : x.map f ∣ y.map f ↔ x ∣ y :=
  if H : x = 0 then by rw [H, Polynomial.map_zero, zero_dvd_iff, zero_dvd_iff, map_eq_zero]
  else by
    rw [← normalize_dvd_iff, ← @normalize_dvd_iff R[X], normalize_apply, normalize_apply,
      coe_normUnit_of_ne_zero H, coe_normUnit_of_ne_zero (mt (map_eq_zero f).1 H),
      leadingCoeff_map, ← map_inv₀ f, ← map_C, ← Polynomial.map_mul,
      map_dvd_map _ f.injective (monic_mul_leadingCoeff_inv H)]
#align polynomial.map_dvd_map' Polynomial.map_dvd_map'

-- Porting note: previously could not synthesize NormalisationMonoid R[X]
set_option synthInstance.etaExperiment true in
theorem degree_normalize : degree (normalize p) = degree p := by simp
#align polynomial.degree_normalize Polynomial.degree_normalize

-- Porting note: previously could not synthesize NormalisationMonoid R[X]
set_option synthInstance.etaExperiment true in
theorem prime_of_degree_eq_one (hp1 : degree p = 1) : Prime p :=
  have : Prime (normalize p) :=
    Monic.prime_of_degree_eq_one (hp1 ▸ degree_normalize)
      (monic_normalize fun hp0 => absurd hp1 (hp0.symm ▸ by simp))
  (normalize_associated _).prime this
#align polynomial.prime_of_degree_eq_one Polynomial.prime_of_degree_eq_one

theorem irreducible_of_degree_eq_one (hp1 : degree p = 1) : Irreducible p :=
  (prime_of_degree_eq_one hp1).irreducible
#align polynomial.irreducible_of_degree_eq_one Polynomial.irreducible_of_degree_eq_one

theorem not_irreducible_c (x : R) : ¬Irreducible (C x) :=
  if H : x = 0 then by
    rw [H, C_0]
    exact not_irreducible_zero
  else fun hx => Irreducible.not_unit hx <| isUnit_C.2 <| isUnit_iff_ne_zero.2 H
set_option linter.uppercaseLean3 false in
#align polynomial.not_irreducible_C Polynomial.not_irreducible_c

theorem degree_pos_of_irreducible (hp : Irreducible p) : 0 < p.degree :=
  lt_of_not_ge fun hp0 =>
    have := eq_C_of_degree_le_zero hp0
    not_irreducible_c (p.coeff 0) <| this ▸ hp
#align polynomial.degree_pos_of_irreducible Polynomial.degree_pos_of_irreducible

/- Porting note: factored out a have statement from isCoprime_of_is_root_of_eval_derivative_ne_zero
into multiple decls because the original proof was timing out -/
theorem X_sub_C_mul_divByMonic_eq_sub_modByMonic {K : Type _} [Field K] (f : K[X]) (a : K) :
    (X - C a) * (f /ₘ (X - C a)) = f - f %ₘ (X - C a) := by
  rw [eq_sub_iff_add_eq, ← eq_sub_iff_add_eq', modByMonic_eq_sub_mul_div]
  exact monic_X_sub_C a

/- Porting note: factored out a have statement from isCoprime_of_is_root_of_eval_derivative_ne_zero
because the original proof was timing out -/
theorem divByMonic_add_X_Sub_C_mul_derivate_divByMonic_eq_derivative
    {K : Type _} [Field K] (f : K[X]) (a : K) :
    f /ₘ (X - C a) + (X - C a) * derivative (f /ₘ (X - C a)) = derivative f := by
  have key := by apply congrArg derivative <| X_sub_C_mul_divByMonic_eq_sub_modByMonic f a
  rw [modByMonic_X_sub_C_eq_C_eval] at key
  rw [derivative_mul,derivative_sub,derivative_X,derivative_sub] at key
  rw [derivative_C,sub_zero,one_mul] at key
  rw [derivative_C,sub_zero] at key
  assumption

/- Porting note: factored out another have statement from
isCoprime_of_is_root_of_eval_derivative_ne_zero because the original proof was timing out -/
theorem X_sub_C_dvd_derivative_of_X_sub_C_dvd_divByMonic {K : Type _} [Field K] (f : K[X]) {a : K}
    (hf : (X - C a) ∣ f /ₘ (X - C a)) : X - C a ∣ derivative f := by
  have key := divByMonic_add_X_Sub_C_mul_derivate_divByMonic_eq_derivative f a
  have ⟨u,hu⟩ := hf
  rw [←key,hu,←mul_add (X - C a) u _]
  use (u + derivative ((X - C a) * u))

/-- If `f` is a polynomial over a field, and `a : K` satisfies `f' a ≠ 0`,
then `f / (X - a)` is coprime with `X - a`.
Note that we do not assume `f a = 0`, because `f / (X - a) = (f - f a) / (X - a)`. -/
theorem isCoprime_of_is_root_of_eval_derivative_ne_zero {K : Type _} [Field K] (f : K[X]) (a : K)
    (hf' : f.derivative.eval a ≠ 0) : IsCoprime (X - C a : K[X]) (f /ₘ (X - C a)) := by
  refine Or.resolve_left
      (EuclideanDomain.dvd_or_coprime (X - C a) (f /ₘ (X - C a))
        (irreducible_of_degree_eq_one (Polynomial.degree_X_sub_C a))) ?_
  contrapose! hf' with h
  have : X - C a ∣ derivative f := X_sub_C_dvd_derivative_of_X_sub_C_dvd_divByMonic f h
  rw [← dvd_iff_modByMonic_eq_zero (monic_X_sub_C _), modByMonic_X_sub_C_eq_C_eval] at this
  rw [← C_inj, C_0]
  assumption
#align polynomial.is_coprime_of_is_root_of_eval_derivative_ne_zero Polynomial.isCoprime_of_is_root_of_eval_derivative_ne_zero

end Field

end Polynomial


variables (α : Type) (p q : α → Prop)
variable r : Prop

example : α → ((∀ x : α, r) ↔ r) :=
    assume y: α,
    iff.intro
        (
            assume h: (∀ x: α, r),
            show r, from h y
        )
        (
            assume h: r,
            assume x: α,
            h
        )

-- example : (∀ x, p x ∨ r) ↔ (∀ x, p x) ∨ r := sorry
-- left to right requires classical logic
example : (∀ x, p x) ∨ r → (∀ x, p x ∨ r) :=
    assume h: (∀ x, p x) ∨ r,
    or.elim h
        (
            assume h₁: ∀ x, p x,
            assume y: α,
            or.intro_left r (h₁ y)
        )
        (
            assume h₂: r,
            assume y: α,
            or.intro_right (p y) h₂
        )

example : (∀ x, r → p x) ↔ (r → ∀ x, p x) :=
    iff.intro
        (
            assume h: ∀ x, r → p x,
            assume hr: r,
            assume y: α,
            show p y, from (h y) hr
        )
        (
            assume h: r → ∀ x, p x,
            assume y: α,
            assume hr: r,
            show p y, from (h hr) y
        )