/-
Copyright (c) 2022 Kevin Buzzard. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Author : Kevin Buzzard
-/

import tactic -- imports all the Lean tactics

/-!

# Sets in Lean, example sheet 3 : not in (`∉`) and complement `Aᶜ`

The definition in Lean of `x ∉ A` is `¬ (x ∈ A)`. In other words,
`x ∉ A`, `¬ (x ∈ A)` and `(x ∈ A) → false` are all equal *by definition*
in Lean.

The complement of a subset `A` of `X` is the subset `Aᶜ`; it's the terms of
type `X` which aren't in `A`. The *definition* of `x ∈ Aᶜ` is `x ∉ A`.

For example, if you have a hypothesis `h : x ∈ Aᶜ` and your goal
is `false`, then `apply h` will work and will change the goal to `x ∈ A`.
Think a bit about why.

-/

open set

variables
  (X : Type) -- Everything will be a subset of `X`
  (A B C D E : set X) -- A,B,C,D,E are subsets of `X`
  (x y z : X) -- x,y,z are elements of `X` or, more precisely, terms of type `X`

example : x ∉ A → (x ∈ A → false) :=
begin
  sorry
end

example : x ∈ A → (x ∉ A → false) :=
begin
  sorry
end

example : (A ⊆ B) → x ∉ B → x ∉ A :=
begin
  sorry
end

-- Lean couldn't work out what I meant when I wrote `x ∈ ∅` so I had
-- to give it a hint by telling it the type of `∅`.
example : x ∉ (∅ : set X):=
begin
  sorry
end

example : x ∈ Aᶜ → x ∉ A :=
begin
  sorry
end

example : (∀ x, x ∈ A) ↔ ¬ (∃ x, x ∈ Aᶜ) :=
begin
  sorry
end

example : (∃ x, x ∈ A) ↔ ¬ (∀ x, x ∈ Aᶜ) :=
begin
  sorry
end


-- La composición de funciones impares es impar
-- ============================================

import data.real.basic

variables (f g : ℝ → ℝ)

-- ----------------------------------------------------
-- Ejercicio 1. Definir la función
--    impar : (ℝ → ℝ) → Prop
-- tal que (impar f) expresa que f es impar.
-- ----------------------------------------------------

def impar (f : ℝ → ℝ) : Prop :=
∀ x, f (-x) = -f x

-- ----------------------------------------------------
-- Ejercicio 2. Demostrar que la composición de
-- funciones impares es impar.
-- ----------------------------------------------------

-- 1ª demostración
example :
  impar f → impar g →  impar (g ∘ f) :=
begin
  intros hf hg,
  unfold impar at *,
  intro a,
  unfold function.comp,
  specialize hf a,
  rw hf,
  specialize hg (f a),
  rw hg,
end

-- 2ª demostración
example :
  impar f → impar g →  impar (g ∘ f) :=
begin
  intros hf hg a,
  simp,
  rw hf,
  rw hg,
end

-- 3ª demostración
example :
  impar f → impar g →  impar (g ∘ f) :=
begin
  intros hf hg a,
  simp,
  rw [hf, hg],
end

-- 4ª demostración
example :
  impar f → impar g →  impar (g ∘ f) :=
begin
  intros hf hg x,
  calc (g ∘ f) (-x)
       = g (f (-x))  : rfl
   ... = g (- f x)   : by rw hf
   ... = -(g (f x))  : by rw hg
   ... = -(g ∘ f) x  : rfl,
end

-- 5ª demostración
example :
  impar f → impar g →  impar (g ∘ f) :=
begin
  intros hf hg x,
  calc (g ∘ f) (-x)
       = g (f (-x))  : rfl
   ... = -(g (f x))  : by rw [hf, hg]
end


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

/-!
# Conformal maps between inner product spaces

In an inner product space, a map is conformal iff it preserves inner products up to a scalar factor.
-/

variables {E F : Type*}
variables [normed_add_comm_group E] [normed_add_comm_group F]
variables [inner_product_space ℝ E] [inner_product_space ℝ F]

open linear_isometry continuous_linear_map
open_locale real_inner_product_space

/-- A map between two inner product spaces is a conformal map if and only if it preserves inner
products up to a scalar factor, i.e., there exists a positive `c : ℝ` such that `⟪f u, f v⟫ = c *
⟪u, v⟫` for all `u`, `v`. -/
lemma is_conformal_map_iff (f : E →L[ℝ] F) :
  is_conformal_map f ↔ ∃ (c : ℝ), 0 < c ∧ ∀ (u v : E), ⟪f u, f v⟫ = c * ⟪u, v⟫ :=
begin
  split,
  { rintros ⟨c₁, hc₁, li, rfl⟩,
    refine ⟨c₁ * c₁, mul_self_pos.2 hc₁, λ u v, _⟩,
    simp only [real_inner_smul_left, real_inner_smul_right, mul_assoc, coe_smul',
      coe_to_continuous_linear_map, pi.smul_apply, inner_map_map] },
  { rintros ⟨c₁, hc₁, huv⟩,
    obtain ⟨c, hc, rfl⟩ : ∃ c : ℝ, 0 < c ∧ c₁ = c * c,
      from ⟨real.sqrt c₁, real.sqrt_pos.2 hc₁, (real.mul_self_sqrt hc₁.le).symm⟩,
    refine ⟨c, hc.ne', (c⁻¹ • f : E →ₗ[ℝ] F).isometry_of_inner (λ u v, _), _⟩,
    { simp only [real_inner_smul_left, real_inner_smul_right, huv, mul_assoc, coe_smul,
        inv_mul_cancel_left₀ hc.ne', linear_map.smul_apply, continuous_linear_map.coe_coe] },
    { ext1 x,
      exact (smul_inv_smul₀ hc.ne' (f x)).symm } }
end


-- Minimo_de_suma.lean
-- Si a, b, c ∈ ℝ, entonces min a b + c = min (a + c) (b + c)
-- José A. Alonso Jiménez <https://jaalonso.github.io>
-- Sevilla, 4-octubre-2022
-- ---------------------------------------------------------------------

-- ---------------------------------------------------------------------
-- Sean a, b y c números reales. Demostrar que
--    min a b + c = min (a + c) (b + c)
-- ----------------------------------------------------------------------

import data.real.basic

variables a b c : ℝ

-- 1ª demostración
-- ===============

lemma aux1a :
  min a b + c ≤ min (a + c) (b + c) :=
begin
  have h1 : min a b  ≤ a :=
    min_le_left a b,
  have h2 : min a b + c ≤ a + c :=
    add_le_add_right h1 c,
  have h3 : min a b  ≤ b :=
    min_le_right a b,
  have h4 : min a b + c ≤ b + c :=
    add_le_add_right h3 c,
  show min a b + c ≤ min (a + c) (b + c),
    by exact le_min h2 h4,
end

lemma aux2a :
  min (a + c) (b + c) ≤ min a b + c :=
begin
  have h1 : min (a + c) (b + c) + -c ≤ min a b,
  { calc min (a + c) (b + c) + -c
         ≤ min (a + c + -c) (b + c + -c) : aux1a (a + c) (b + c) (-c)
     ... = min a b                       : by ring_nf },
  show min (a + c) (b + c) ≤ min a b + c,
    by exact add_neg_le_iff_le_add.mp h1,
end

example :
  min a b + c = min (a + c) (b + c) :=
begin
  apply le_antisymm,
  { exact aux1a a b c, },
  { exact aux2a a b c, },
end

-- 2ª demostración
-- ===============

lemma aux1b :
  min a b + c ≤ min (a + c) (b + c) :=
begin
  apply le_min,
  { apply add_le_add_right,
    exact min_le_left a b, },
  { apply add_le_add_right,
    exact min_le_right a b, },
end

lemma aux2b :
  min (a + c) (b + c) ≤ min a b + c :=
begin
  have h1 : min (a + c) (b + c) + -c ≤ min a b,
  { calc min (a + c) (b + c) + -c
         ≤ min (a + c + -c) (b + c + -c) : aux1b (a + c) (b + c) (-c)
     ... = min a b                       : by ring_nf },
  exact add_neg_le_iff_le_add.mp h1,
end

example :
  min a b + c = min (a + c) (b + c) :=
begin
  apply le_antisymm,
  { exact aux1b a b c, },
  { exact aux2b a b c, },
end

-- 3ª demostración
-- ===============

lemma aux1c :
  min a b + c ≤ min (a + c) (b + c) :=
le_min (add_le_add_right (min_le_left a b) c)
       (add_le_add_right (min_le_right a b) c)

lemma aux2c :
  min (a + c) (b + c) ≤ min a b + c :=
begin
  have h1 : min (a + c) (b + c) + -c ≤ min a b,
  { calc min (a + c) (b + c) + -c
         ≤ min (a + c + -c) (b + c + -c) : aux1c (a + c) (b + c) (-c)
     ... = min a b                       : by ring_nf },
  exact add_neg_le_iff_le_add.mp h1,
end

example :
  min a b + c = min (a + c) (b + c) :=
le_antisymm (aux1b a b c) (aux2b a b c)

-- 4ª demostración
-- ===============

example : min a b + c = min (a + c) (b + c) :=
begin
  by_cases (a ≤ b),
  { have h1 : a + c ≤ b + c,
      apply add_le_add_right h,
    calc min a b + c = a + c               : by simp [min_eq_left h]
                 ... = min (a + c) (b + c) : by simp [min_eq_left h1]},
  { have h2: b ≤ a,
      linarith,
    have h3 : b + c ≤ a + c,
      { exact add_le_add_right h2 c },
    calc min a b + c = b + c               : by simp [min_eq_right h2]
                 ... = min (a + c) (b + c) : by simp [min_eq_right h3]},
end

-- 5ª demostración
-- ===============

example : min a b + c = min (a + c) (b + c) :=
(min_add_add_right a b c).symm


-- Opuesto_del_opuesto.lean
-- Si R es un anillo y a ∈ R, entonces -(-a) = a.
-- José A. Alonso Jiménez <https://jaalonso.github.io>
-- Sevilla, 9-septiembre-2022
-- ---------------------------------------------------------------------

-- ---------------------------------------------------------------------
-- Demostrar que si R es un anillo y a ∈ R, entonces
--    -(-a) = a
-- ----------------------------------------------------------------------

import algebra.ring

variables {R : Type*} [ring R]
variable a : R

-- 1ª demostración
-- ===============

example : -(-a) = a :=
begin
  calc -(-a)
       = -(0 - a) : congr_arg (λ x, -x) (zero_sub a).symm
   ... = a - 0    : neg_sub (0 : R) a
   ... = a        : sub_zero a
end

-- 2ª demostración
-- ===============

example : -(-a) = a :=
begin
  calc -(-a)
       = -(0 - a) : by { congr; rw zero_sub}
   ... = a - 0    : by rw neg_sub
   ... = a        : by rw sub_zero
end

-- 3ª demostración
-- ===============

example : -(-a) = a :=
by simpa only [zero_sub, sub_zero] using (neg_sub (0 : R) a)

-- 4ª demostración
-- ===============

example : -(-a) = a :=
neg_neg a

-- 5ª demostración
-- ===============

example : -(-a) = a :=
by simp

-- 6ª demostración
-- ===============

example : -(-a) = a :=
begin
  apply neg_eq_of_add_eq_zero_right,
  rw neg_add_self a,
end

-- 7ª demostración
-- ===============

example : -(-a) = a :=
neg_eq_of_add_eq_zero_right (neg_add_self a)


/-
Copyright (c) 2022 Michael Stoll. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Michael Stoll
-/
import ring_theory.trace
import field_theory.finite.basic
import field_theory.finite.galois_field

/-!
# The trace map for finite fields

We state the fact that the trace map from a finite field of
characteristic `p` to `zmod p` is nondegenerate.

## Tags
finite field, trace
-/

namespace finite_field

/-- The trace map from a finite field to its prime field is nongedenerate. -/
lemma trace_to_zmod_nondegenerate (F : Type*) [field F] [fintype F] {a : F}
 (ha : a ≠ 0) : ∃ b : F, algebra.trace (zmod (ring_char F)) F (a * b) ≠ 0 :=
begin
  haveI : fact (ring_char F).prime := ⟨char_p.char_is_prime F _⟩,
  have htr := trace_form_nondegenerate (zmod (ring_char F)) F a,
  simp_rw [algebra.trace_form_apply] at htr,
  by_contra' hf,
  exact ha (htr hf),
end

end finite_field


/-
Terminology: INTRODUCTION RULES
-/

/-
The constructors of a type implement
what in logic and computation we can
call *introduction* rules. They give
us ways to *introduce* vaues of some
particular type.
-/

/-
Inference rule notation.
-/

/-
There are two introduction rules for bool

  Γ ⊢ bool.tt : bool   -- bool_intro_tt
  Γ ⊢ bool.ff : bool   -- bool_intro_ff

The capital Greek letter Gamma (Γ) stands
for an set of definitions assumed already
to be given. The turnstile character (⊢)
means that "in this context it is possible
to deduce the "type judgement" after the
turnstile. Concretely, here, the rules say
that "in any context, without any further
assumptions, we can deduce that bool.tt is
a value of type bool (and the same goes
for bool.ff).

In logical terms, you can read such a rule
as *asserting* that if the definitions in
the context are assumed to be true (we can
think of these as premises or preconditions)
then it's possible to derive the truth of
the "judgment" that comes below the line
(after the turnstile).

The preceding notation above is "inline"
notation (context ⊢ judgment) and is used
for giving short inference rules. Dispay
notation, illustrated below, is generally
used intead when rules are too long to be
readable on one line. Here the context is
above the line; the line is used instead
of the turnstile; a judgment is given below
the line; and the name of the inference
rule is given to the right of the line.
We will often attach _intro or _I to the
name of a rule to indicate that it's an
introduction rule.

  Γ
------ bool_intro.tt
 bool

   Γ
------ bool_intro.ff
 bool

Note also that if there really are no
preconditions on the context for the
application of a rule, the Γ is often
left out.

------ bool_intro.tt
 bool

------ bool_intro.ff
 bool

In logic, an introduction rule with
no preconditions is called an axiom.
It allows us to make certain judgments
without already knowing anything else
at all.

The constructors of inductive types
can be read as type-speific axioms.
The constructor "| tt : bool", for
example, says that are given the type
judgment, tt : bool, "for free". We
can assume it's true with the need
for any prior or additional evidence.

EXERCISE: How many introduction rules
are there for the day type?

Here are introduction rules for some
of our other types

While constructors can be read as axioms,
which is to say that we can always assume
they're valid, sometimes to apply or use
such a rule, we do need certain additional
assumptions to be true.

Consider the box constructor, mk. A way
to read it is that "If we may assume that
n is a value of type ℕ then we may infer
that the term (box_ℕ.mk n) is of type
box_ℕ."

Γ, (n : ℕ) ⊢ (box_ℕ.mk n) : box_ℕ

     Γ, (n : ℕ)
---------------------- box_ℕ_intro
 (box_ℕ.mk n) : box_ℕ

 Note that such rules often leave out
 the full statement of the type judgment
 below the line (after the turnstile).Type

      Γ, (n : ℕ)
---------------------- box_ℕ_intro
        box_ℕ

This just says that in a context in which
I have that n is of type nat, I can use the
box_ℕ_intro rule to construct a value of
type box_ℕ.

 NB! There's really nothing new here
 at all. All we're really doing here
 it representing the *types* of available
 constructors (introduction rules) using
 a sort of textual notation (inline or
 display). Here's one of the ways we
 wrote the same information above.

| mk (n : ℕ) : box_ℕ

Constructors are introduction rules
that we take as axioms, i.e., as being
given, defined, and available for use.
That said, these axioms are often in
the form of implications that do require
some additional conditions to be true
before they can be used to derive given
conclusions. For example, the box_ℕ
introduction rule does require that we
can assume that (n : ℕ). You can think
of *arguments* to constructors (and to
functions more generally) as assumptions
that have to be satisfied before the
constructor or function can be used!

Constructors are introductions rules
that are accepted as axioms

Arguments to constructors are additional
assumptions that have to be true before
such rules can be applied to derive the
given conclusions.

In general, inference rules can require
that multiple assumptions be true before
a conclusion can be drawn.

Γ, (n : ℕ) (s : string)
----------------------- prod_ℕ_string_intro
(mk n s) : prod_ℕ_string

Give a context containing at least the
assumptions that (n : ℕ) and (s : string)
this inference rules allows us to deduce
that we can construct and object (mk n s)
of type prod_ℕ_string.

Constructors are "introduction" inference
rules that end with the construction of a
a new object with the specified type.
-/

/-
There's really nothing mysterious about
this notation. It's basically a way of
explaining/claiming that in a specified
context, i.e., contingent on a given set
of assumptions, some object of a certain
type can be constructed.
-/

/-
Terminology: ELIMINATION RULES

Where introduction rules explain how we can
form new terms (objects, values) of specific
types, ELIMINAATION rules tell us how we can
USE objects of given type (to form objects of
other types).

To use an object of some type, we often need to
DESTRUCTURE it: to take it apart to see both
the constructor that was used to form it and
the arguments (if any) that were supplied to
the constructor when the object was created.

Consider the following elimination rule.

         Γ, (d : day)
---------------------------- day_elim
(e, p) : Σ (d : day), e ≠ d

We assume we're in a context in which we're
given a day d, and we then "use" d to pick
a different day, e, which we return as the
first element of a pair, along with evidence
that e really is a different day.

Can we prove to ourselves that this rule is
actually realizable? It seems like it should
be possible. For example, given a day, d, we
can always pick "the next" day as e, and then
we just need a little "proof" that e really
is not the same day as d.

Now here's a crucial point. To use a value
of a sum type, here of type day, we will often
have to perform a case analysis, to determine
which of several possible "forms" of value
we're looking at (which constructor was used
to introduce the value in the first place).
-/

def next_day : day → day
| sun := mon
| mon := tue
| tue := wed
| wed := thu
| thu := fri
| fri := sat
| sat := sun

#reduce next_day tue

def exists_different_day (d : day) : ∃ (e : day), e ≠ d :=
  exists.intro
    (next_day d)
    (by begin
      cases d,          -- consider each possible day, d
      simp [next_day],  -- apply rules in next_day + "magic"
      simp [next_day],  -- do the same thing for each case
      simp [next_day],
      simp [next_day],
      simp [next_day],
      simp [next_day],
      simp [next_day],
  end)

#reduce (exists_different_day sat)    -- Yikes :-)

/-
Lots of things to say, here:

1. Inference rule notation

      Γ, (d : day)
----------------------- day_elim_different_day
pf : ∃ (e : day), e ≠ d

2. Lean is language in which we can compute with
logical propositions and proofs just as easily as
we can compute with natural numbers, bools, strings,
and so forth. This puts it in a completely different
class of languages than mere functional languages,
such as Haskell, OCaml, Scheme, etc.

3. We see in this example that in the logical framework
that Lean presents, propositions are represented as
types, and proofs of propositions are simply values of
such types. Moreover, the type checker ensures that
proofs are of the right types -- that the prove the
propositions they are claimed to prove. If P is a type
that represents a proposition, and we have (pf : P),
just as if (n : ℕ) type checks, we can be *sure* that
n really is a term of type ℕ!

4. Clearly it's going to be super-important to learn
about types. In fact, Lean is really just implements
one particular version of the theoretical field of CS
called type theory.

5. Type theory is so expressive that it provides us
with what we call a logical framework, which is a
language within which we can define and automate many
other logics, and many other languages, more broadly,
including such languages as C, Python, etc. We have
already implemented the syntax and semantics of one
very simple language of (a few) Roman numberals.

6. Note that we picked a particular implementation,
next day, to pick a "witness" to the proposition that
there's a different day for each possible argument of
type day. The witness selected when d := wed, for
example, is thu. We could have used a different
witness-picking function, such as one that returns
the previous day, as long as it really does *always*
return a day that's different than the one given as
an argument.

7. If our witness-picking function has a bug, then
our proof-constructing function will fail, because
there will be some day for which the chooser will
choose them same day as a "e", the intended witness,
and it will then be impossible to construct the proof
that e ≠ d. The lean proof checker can can find bugs
in programs relative to a logical *specifications* of
their required behaviors.

Exercise: make a small change to next_day so that
it no longer *always* returns a day different from
the one given, and then find exactly where the type
checker raises an error.

Phew, that was a lot! Some of you don't even have a
firm grasp of what we mean by a proposition or a proof.
No worries, this was just a little "exposure." Are you
feeling sunburned?

To bring it back to where we started, elimination rules
are simply "rules" (often just funtions) for *consuming*
rather than *constructing* values of a given type.

Ehen the type of object being consumed is a sum type,
we generally need to do a case analysis to determine
which variant we've been given as a value.

The next_day function is a good example: it consumes
a *day* value, does a case analysis to figure out which
constructor/variant is assumed to have been given as an
argument, then computes and returns a result accordingly.

As a second example of a day-elimination (day-consuming)
function/rule, we defined different_day as a function
that uses/consumes a value of type day and returns (if
you prefer, introduces) a value that proves that there
is *some* day different than the one consumed.
-/

-- ELIMINATION/USE OF VALUES OF PRODUCT TYPES

/-
Inference rule notation

 Γ, (n : ℕ) (s : string)
------------------------ prod_intro
 ⟨n, s⟩ : Prod_ℕ_string

Γ, ⟨n, s⟩ : prod_ℕ_string
------------------------- prod_elim_fst
        n : ℕ

Γ, ⟨n, s⟩ : prod_ℕ_string
------------------------- prod_elim_snd
        s : string
-/

end hidden