/-
Copyright (c) 2014 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Mario Carneiro

! This file was ported from Lean 3 source module data.num.bitwise
! leanprover-community/mathlib commit 68d1483e8a718ec63219f0e227ca3f0140361086
! Please do not edit these lines, except to modify the commit id
! if you have ported upstream changes.
-/
import Mathbin.Data.Num.Basic
import Mathbin.Data.Bitvec.Core

/-!
# Bitwise operations using binary representation of integers

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

## Definitions

* bitwise operations for `pos_num` and `num`,
* `snum`, a type that represents integers as a bit string with a sign bit at the end,
* arithmetic operations for `snum`.
-/

namespace PosNum

#print PosNum.lor /-
/-- Bitwise "or" for `pos_num`. -/
def lor : PosNum → PosNum → PosNum
  | 1, bit0 q => bit1 q
  | 1, q => q
  | bit0 p, 1 => bit1 p
  | p, 1 => p
  | bit0 p, bit0 q => bit0 (lor p q)
  | bit0 p, bit1 q => bit1 (lor p q)
  | bit1 p, bit0 q => bit1 (lor p q)
  | bit1 p, bit1 q => bit1 (lor p q)
#align pos_num.lor PosNum.lor
-/

#print PosNum.land /-
/-- Bitwise "and" for `pos_num`. -/
def land : PosNum → PosNum → Num
  | 1, bit0 q => 0
  | 1, _ => 1
  | bit0 p, 1 => 0
  | _, 1 => 1
  | bit0 p, bit0 q => Num.bit0 (land p q)
  | bit0 p, bit1 q => Num.bit0 (land p q)
  | bit1 p, bit0 q => Num.bit0 (land p q)
  | bit1 p, bit1 q => Num.bit1 (land p q)
#align pos_num.land PosNum.land
-/

#print PosNum.ldiff /-
/-- Bitwise `λ a b, a && !b` for `pos_num`. For example, `ldiff 5 9 = 4`:

     101
    1001
    ----
     100

  -/
def ldiff : PosNum → PosNum → Num
  | 1, bit0 q => 1
  | 1, _ => 0
  | bit0 p, 1 => Num.pos (bit0 p)
  | bit1 p, 1 => Num.pos (bit0 p)
  | bit0 p, bit0 q => Num.bit0 (ldiff p q)
  | bit0 p, bit1 q => Num.bit0 (ldiff p q)
  | bit1 p, bit0 q => Num.bit1 (ldiff p q)
  | bit1 p, bit1 q => Num.bit0 (ldiff p q)
#align pos_num.ldiff PosNum.ldiff
-/

#print PosNum.lxor /-
/-- Bitwise "xor" for `pos_num`. -/
def lxor : PosNum → PosNum → Num
  | 1, 1 => 0
  | 1, bit0 q => Num.pos (bit1 q)
  | 1, bit1 q => Num.pos (bit0 q)
  | bit0 p, 1 => Num.pos (bit1 p)
  | bit1 p, 1 => Num.pos (bit0 p)
  | bit0 p, bit0 q => Num.bit0 (lxor p q)
  | bit0 p, bit1 q => Num.bit1 (lxor p q)
  | bit1 p, bit0 q => Num.bit1 (lxor p q)
  | bit1 p, bit1 q => Num.bit0 (lxor p q)
#align pos_num.lxor PosNum.lxor
-/

#print PosNum.testBit /-
/-- `a.test_bit n` is `tt` iff the `n`-th bit (starting from the LSB) in the binary representation
      of `a` is active. If the size of `a` is less than `n`, this evaluates to `ff`. -/
def testBit : PosNum → Nat → Bool
  | 1, 0 => true
  | 1, n + 1 => false
  | bit0 p, 0 => false
  | bit0 p, n + 1 => test_bit p n
  | bit1 p, 0 => true
  | bit1 p, n + 1 => test_bit p n
#align pos_num.test_bit PosNum.testBit
-/

#print PosNum.oneBits /-
/-- `n.one_bits 0` is the list of indices of active bits in the binary representation of `n`. -/
def oneBits : PosNum → Nat → List Nat
  | 1, d => [d]
  | bit0 p, d => one_bits p (d + 1)
  | bit1 p, d => d :: one_bits p (d + 1)
#align pos_num.one_bits PosNum.oneBits
-/

#print PosNum.shiftl /-
/-- Left-shift the binary representation of a `pos_num`. -/
def shiftl (p : PosNum) : Nat → PosNum
  | 0 => p
  | n + 1 => bit0 (shiftl n)
#align pos_num.shiftl PosNum.shiftl
-/

#print PosNum.shiftr /-
/-- Right-shift the binary representation of a `pos_num`. -/
def shiftr : PosNum → Nat → Num
  | p, 0 => Num.pos p
  | 1, n + 1 => 0
  | bit0 p, n + 1 => shiftr p n
  | bit1 p, n + 1 => shiftr p n
#align pos_num.shiftr PosNum.shiftr
-/

end PosNum

namespace Num

#print Num.lor /-
/-- Bitwise "or" for `num`. -/
def lor : Num → Num → Num
  | 0, q => q
  | p, 0 => p
  | Pos p, Pos q => pos (p.lor' q)
#align num.lor Num.lor
-/

#print Num.land /-
/-- Bitwise "and" for `num`. -/
def land : Num → Num → Num
  | 0, q => 0
  | p, 0 => 0
  | Pos p, Pos q => p.land' q
#align num.land Num.land
-/

#print Num.ldiff /-
/-- Bitwise `λ a b, a && !b` for `num`. For example, `ldiff 5 9 = 4`:

     101
    1001
    ----
     100

  -/
def ldiff : Num → Num → Num
  | 0, q => 0
  | p, 0 => p
  | Pos p, Pos q => p.ldiff' q
#align num.ldiff Num.ldiff
-/

#print Num.lxor /-
/-- Bitwise "xor" for `num`. -/
def lxor : Num → Num → Num
  | 0, q => q
  | p, 0 => p
  | Pos p, Pos q => p.lxor' q
#align num.lxor Num.lxor
-/

#print Num.shiftl /-
/-- Left-shift the binary representation of a `num`. -/
def shiftl : Num → Nat → Num
  | 0, n => 0
  | Pos p, n => pos (p.shiftl n)
#align num.shiftl Num.shiftl
-/

#print Num.shiftr /-
/-- Right-shift the binary representation of a `pos_num`. -/
def shiftr : Num → Nat → Num
  | 0, n => 0
  | Pos p, n => p.shiftr n
#align num.shiftr Num.shiftr
-/

#print Num.testBit /-
/-- `a.test_bit n` is `tt` iff the `n`-th bit (starting from the LSB) in the binary representation
      of `a` is active. If the size of `a` is less than `n`, this evaluates to `ff`. -/
def testBit : Num → Nat → Bool
  | 0, n => false
  | Pos p, n => p.testBit n
#align num.test_bit Num.testBit
-/

#print Num.oneBits /-
/-- `n.one_bits` is the list of indices of active bits in the binary representation of `n`. -/
def oneBits : Num → List Nat
  | 0 => []
  | Pos p => p.oneBits 0
#align num.one_bits Num.oneBits
-/

end Num

#print NzsNum /-
/-- This is a nonzero (and "non minus one") version of `snum`.
    See the documentation of `snum` for more details. -/
inductive NzsNum : Type
  | msb : Bool → NzsNum
  | bit : Bool → NzsNum → NzsNum
  deriving has_reflect, DecidableEq
#align nzsnum NzsNum
-/

#print SNum /-
/-- Alternative representation of integers using a sign bit at the end.
  The convention on sign here is to have the argument to `msb` denote
  the sign of the MSB itself, with all higher bits set to the negation
  of this sign. The result is interpreted in two's complement.

     13  = ..0001101(base 2) = nz (bit1 (bit0 (bit1 (msb tt))))
     -13 = ..1110011(base 2) = nz (bit1 (bit1 (bit0 (msb ff))))

  As with `num`, a special case must be added for zero, which has no msb,
  but by two's complement symmetry there is a second special case for -1.
  Here the `bool` field indicates the sign of the number.

     0  = ..0000000(base 2) = zero ff
     -1 = ..1111111(base 2) = zero tt -/
inductive SNum : Type
  | zero : Bool → SNum
  | nz : NzsNum → SNum
  deriving has_reflect, DecidableEq
#align snum SNum
-/

instance : Coe NzsNum SNum :=
  ⟨SNum.nz⟩

instance : Zero SNum :=
  ⟨SNum.zero false⟩

instance : One NzsNum :=
  ⟨NzsNum.msb true⟩

instance : One SNum :=
  ⟨SNum.nz 1⟩

instance : Inhabited NzsNum :=
  ⟨1⟩

instance : Inhabited SNum :=
  ⟨0⟩

/-!
The `snum` representation uses a bit string, essentially a list of 0 (`ff`) and 1 (`tt`) bits,
and the negation of the MSB is sign-extended to all higher bits.
-/

namespace NzsNum

-- mathport name: nznum.bit
notation a "::" b => bit a b

/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
#print NzsNum.sign /-
/-- Sign of a `nzsnum`. -/
def sign : NzsNum → Bool
  | msb b => not b
  | b::p => SignType.sign p
#align nzsnum.sign NzsNum.sign
-/

/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
#print NzsNum.not /-
/-- Bitwise `not` for `nzsnum`. -/
@[match_pattern]
def not : NzsNum → NzsNum
  | msb b => msb (not b)
  | b::p => not b::Not p
#align nzsnum.not NzsNum.not
-/

-- mathport name: «expr~ »
prefix:100 "~" => not

#print NzsNum.bit0 /-
/-- Add an inactive bit at the end of a `nzsnum`. This mimics `pos_num.bit0`. -/
def bit0 : NzsNum → NzsNum :=
  bit false
#align nzsnum.bit0 NzsNum.bit0
-/

#print NzsNum.bit1 /-
/-- Add an active bit at the end of a `nzsnum`. This mimics `pos_num.bit1`. -/
def bit1 : NzsNum → NzsNum :=
  bit true
#align nzsnum.bit1 NzsNum.bit1
-/

/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
#print NzsNum.head /-
/-- The `head` of a `nzsnum` is the boolean value of its LSB. -/
def head : NzsNum → Bool
  | msb b => b
  | b::p => b
#align nzsnum.head NzsNum.head
-/

/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
#print NzsNum.tail /-
/-- The `tail` of a `nzsnum` is the `snum` obtained by removing the LSB.
      Edge cases: `tail 1 = 0` and `tail (-2) = -1`. -/
def tail : NzsNum → SNum
  | msb b => SNum.zero (not b)
  | b::p => p
#align nzsnum.tail NzsNum.tail
-/

end NzsNum

namespace SNum

open NzsNum

#print SNum.sign /-
/-- Sign of a `snum`. -/
def sign : SNum → Bool
  | zero z => z
  | nz p => p.sign
#align snum.sign SNum.sign
-/

#print SNum.not /-
/-- Bitwise `not` for `snum`. -/
@[match_pattern]
def not : SNum → SNum
  | zero z => zero (not z)
  | nz p => ~p
#align snum.not SNum.not
-/

-- mathport name: snum.not
prefix:0 "~" => not

#print SNum.bit /-
/-- Add a bit at the end of a `snum`. This mimics `nzsnum.bit`. -/
@[match_pattern]
def bit : Bool → SNum → SNum
  | b, zero z => if b = z then zero b else msb b
  | b, nz p => p.bit b
#align snum.bit SNum.bit
-/

-- mathport name: snum.bit
notation a "::" b => bit a b

#print SNum.bit0 /-
/-- Add an inactive bit at the end of a `snum`. This mimics `znum.bit0`. -/
def bit0 : SNum → SNum :=
  bit false
#align snum.bit0 SNum.bit0
-/

#print SNum.bit1 /-
/-- Add an active bit at the end of a `snum`. This mimics `znum.bit1`. -/
def bit1 : SNum → SNum :=
  bit true
#align snum.bit1 SNum.bit1
-/

/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
#print SNum.bit_zero /-
theorem bit_zero (b) : (b::zero b) = zero b := by cases b <;> rfl
#align snum.bit_zero SNum.bit_zero
-/

/- warning: snum.bit_one -> SNum.bit_one is a dubious translation:
lean 3 declaration is
  forall (b : Bool), Eq.{1} SNum (SNum.bit b (SNum.zero (not b))) ((fun (a : Type) (b : Type) [self : HasLiftT.{1, 1} a b] => self.0) NzsNum SNum (HasLiftT.mk.{1, 1} NzsNum SNum (CoeTCₓ.coe.{1, 1} NzsNum SNum (coeBase.{1, 1} NzsNum SNum SNum.hasCoe))) (NzsNum.msb b))
but is expected to have type
  forall (b : Bool), Eq.{1} SNum (SNum.bit b (SNum.zero (Decidable.decide (Not (Eq.{1} Bool b Bool.true)) (instDecidableNot (Eq.{1} Bool b Bool.true) (instDecidableEqBool b Bool.true))))) (SNum.nz (NzsNum.msb b))
Case conversion may be inaccurate. Consider using '#align snum.bit_one SNum.bit_oneₓ'. -/
/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
theorem bit_one (b) : (b::zero (not b)) = msb b := by cases b <;> rfl
#align snum.bit_one SNum.bit_one

end SNum

namespace NzsNum

open SNum

/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
#print NzsNum.drec' /-
/-- A dependent induction principle for `nzsnum`, with base cases
      `0 : snum` and `(-1) : snum`. -/
def drec' {C : SNum → Sort _} (z : ∀ b, C (SNum.zero b)) (s : ∀ b p, C p → C (b::p)) :
    ∀ p : NzsNum, C p
  | msb b => by rw [← bit_one] <;> exact s b (SNum.zero (not b)) (z (not b))
  | bit b p => s b p (drec' p)
#align nzsnum.drec' NzsNum.drec'
-/

end NzsNum

namespace SNum

open NzsNum

#print SNum.head /-
/-- The `head` of a `snum` is the boolean value of its LSB. -/
def head : SNum → Bool
  | zero z => z
  | nz p => p.headI
#align snum.head SNum.head
-/

#print SNum.tail /-
/-- The `tail` of a `snum` is obtained by removing the LSB.
      Edge cases: `tail 1 = 0`, `tail (-2) = -1`, `tail 0 = 0` and `tail (-1) = -1`. -/
def tail : SNum → SNum
  | zero z => zero z
  | nz p => p.tail
#align snum.tail SNum.tail
-/

/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
#print SNum.drec' /-
/-- A dependent induction principle for `snum` which avoids relying on `nzsnum`. -/
def drec' {C : SNum → Sort _} (z : ∀ b, C (SNum.zero b)) (s : ∀ b p, C p → C (b::p)) : ∀ p, C p
  | zero b => z b
  | nz p => p.drec' z s
#align snum.drec' SNum.drec'
-/

#print SNum.rec' /-
/-- An induction principle for `snum` which avoids relying on `nzsnum`. -/
def rec' {α} (z : Bool → α) (s : Bool → SNum → α → α) : SNum → α :=
  drec' z s
#align snum.rec' SNum.rec'
-/

#print SNum.testBit /-
/-- `snum.test_bit n a` is `tt` iff the `n`-th bit (starting from the LSB) of `a` is active.
      If the size of `a` is less than `n`, this evaluates to `ff`. -/
def testBit : Nat → SNum → Bool
  | 0, p => head p
  | n + 1, p => test_bit n (tail p)
#align snum.test_bit SNum.testBit
-/

/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
#print SNum.succ /-
/-- The successor of a `snum` (i.e. the operation adding one). -/
def succ : SNum → SNum :=
  rec' (fun b => cond b 0 1) fun b p succp => cond b (false::succp) (true::p)
#align snum.succ SNum.succ
-/

/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
#print SNum.pred /-
/-- The predecessor of a `snum` (i.e. the operation of removing one). -/
def pred : SNum → SNum :=
  rec' (fun b => cond b (~1) (~0)) fun b p predp => cond b (false::p) (true::predp)
#align snum.pred SNum.pred
-/

/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
#print SNum.neg /-
/-- The opposite of a `snum`. -/
protected def neg (n : SNum) : SNum :=
  succ (~n)
#align snum.neg SNum.neg
-/

instance : Neg SNum :=
  ⟨SNum.neg⟩

#print SNum.czAdd /-
/-- `snum.czadd a b n` is `n + a - b` (where `a` and `b` should be read as either 0 or 1).
      This is useful to implement the carry system in `cadd`. -/
def czAdd : Bool → Bool → SNum → SNum
  | ff, ff, p => p
  | ff, tt, p => pred p
  | tt, ff, p => succ p
  | tt, tt, p => p
#align snum.czadd SNum.czAdd
-/

end SNum

namespace SNum

#print SNum.bits /-
/-- `a.bits n` is the vector of the `n` first bits of `a` (starting from the LSB). -/
def bits : SNum → ∀ n, Vector Bool n
  | p, 0 => Vector.nil
  | p, n + 1 => head p ::ᵥ bits (tail p) n
#align snum.bits SNum.bits
-/

/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
/- ./././Mathport/Syntax/Translate/Expr.lean:177:8: unsupported: ambiguous notation -/
#print SNum.cAdd /-
def cAdd : SNum → SNum → Bool → SNum :=
  rec' (fun a p c => czAdd c a p) fun a p IH =>
    rec' (fun b c => czAdd c b (a::p)) fun b q _ c => Bitvec.xor3 a b c::IH q (Bitvec.carry a b c)
#align snum.cadd SNum.cAdd
-/

#print SNum.add /-
/-- Add two `snum`s. -/
protected def add (a b : SNum) : SNum :=
  cAdd a b false
#align snum.add SNum.add
-/

instance : Add SNum :=
  ⟨SNum.add⟩

#print SNum.sub /-
/-- subtract two `snum`s. -/
protected def sub (a b : SNum) : SNum :=
  a + -b
#align snum.sub SNum.sub
-/

instance : Sub SNum :=
  ⟨SNum.sub⟩

#print SNum.mul /-
/-- Multiply two `snum`s. -/
protected def mul (a : SNum) : SNum → SNum :=
  rec' (fun b => cond b (-a) 0) fun b q IH => cond b (bit0 IH + a) (bit0 IH)
#align snum.mul SNum.mul
-/

instance : Mul SNum :=
  ⟨SNum.mul⟩

end SNum


/-
Copyright (c) 2021 Alex J. Best. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Alex J. Best, Yaël Dillies
-/
import algebra.bounds
import data.set.pointwise.smul

/-!
# Pointwise operations on ordered algebraic objects

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

This file contains lemmas about the effect of pointwise operations on sets with an order structure.

## TODO

`Sup (s • t) = Sup s • Sup t` and `Inf (s • t) = Inf s • Inf t` hold as well but
`covariant_class` is currently not polymorphic enough to state it.
-/

open function set
open_locale pointwise

variables {α : Type*}

section conditionally_complete_lattice
variables [conditionally_complete_lattice α]

section has_one
variables [has_one α]

@[simp, to_additive] lemma cSup_one : Sup (1 : set α) = 1 := cSup_singleton _
@[simp, to_additive] lemma cInf_one : Inf (1 : set α) = 1 := cInf_singleton _

end has_one

section group
variables [group α] [covariant_class α α (*) (≤)] [covariant_class α α (swap (*)) (≤)] {s t : set α}

@[to_additive] lemma cSup_inv (hs₀ : s.nonempty) (hs₁ : bdd_below s) : Sup s⁻¹ = (Inf s)⁻¹ :=
by { rw ←image_inv, exact ((order_iso.inv α).map_cInf' hs₀ hs₁).symm }

@[to_additive] lemma cInf_inv (hs₀ : s.nonempty) (hs₁ : bdd_above s) : Inf s⁻¹ = (Sup s)⁻¹ :=
by { rw ←image_inv, exact ((order_iso.inv α).map_cSup' hs₀ hs₁).symm }

@[to_additive] lemma cSup_mul (hs₀ : s.nonempty) (hs₁ : bdd_above s) (ht₀ : t.nonempty)
  (ht₁ : bdd_above t) :
  Sup (s * t) = Sup s * Sup t :=
cSup_image2_eq_cSup_cSup (λ _, (order_iso.mul_right _).to_galois_connection)
  (λ _, (order_iso.mul_left _).to_galois_connection) hs₀ hs₁ ht₀ ht₁

@[to_additive] lemma cInf_mul (hs₀ : s.nonempty) (hs₁ : bdd_below s) (ht₀ : t.nonempty)
  (ht₁ : bdd_below t) :
  Inf (s * t) = Inf s * Inf t :=
cInf_image2_eq_cInf_cInf (λ _, (order_iso.mul_right _).symm.to_galois_connection)
  (λ _, (order_iso.mul_left _).symm.to_galois_connection) hs₀ hs₁ ht₀ ht₁

@[to_additive] lemma cSup_div (hs₀ : s.nonempty) (hs₁ : bdd_above s) (ht₀ : t.nonempty)
  (ht₁ : bdd_below t) :
  Sup (s / t) = Sup s / Inf t :=
by rw [div_eq_mul_inv, cSup_mul hs₀ hs₁ ht₀.inv ht₁.inv, cSup_inv ht₀ ht₁, div_eq_mul_inv]

@[to_additive] lemma cInf_div (hs₀ : s.nonempty) (hs₁ : bdd_below s) (ht₀ : t.nonempty)
  (ht₁ : bdd_above t) :
  Inf (s / t) = Inf s / Sup t :=
by rw [div_eq_mul_inv, cInf_mul hs₀ hs₁ ht₀.inv ht₁.inv, cInf_inv ht₀ ht₁, div_eq_mul_inv]

end group
end conditionally_complete_lattice

section complete_lattice
variables [complete_lattice α]

section has_one
variables [has_one α]

@[simp, to_additive] lemma Sup_one : Sup (1 : set α) = 1 := Sup_singleton
@[simp, to_additive] lemma Inf_one : Inf (1 : set α) = 1 := Inf_singleton

end has_one

section group
variables [group α] [covariant_class α α (*) (≤)] [covariant_class α α (swap (*)) (≤)] (s t : set α)

@[to_additive] lemma Sup_inv (s : set α) : Sup s⁻¹ = (Inf s)⁻¹ :=
by { rw [←image_inv, Sup_image], exact ((order_iso.inv α).map_Inf _).symm }

@[to_additive] lemma Inf_inv (s : set α) : Inf s⁻¹ = (Sup s)⁻¹ :=
by { rw [←image_inv, Inf_image], exact ((order_iso.inv α).map_Sup _).symm }

@[to_additive] lemma Sup_mul : Sup (s * t) = Sup s * Sup t :=
Sup_image2_eq_Sup_Sup (λ _, (order_iso.mul_right _).to_galois_connection) $
  λ _, (order_iso.mul_left _).to_galois_connection

@[to_additive] lemma Inf_mul : Inf (s * t) = Inf s * Inf t :=
Inf_image2_eq_Inf_Inf (λ _, (order_iso.mul_right _).symm.to_galois_connection) $
  λ _, (order_iso.mul_left _).symm.to_galois_connection

@[to_additive] lemma Sup_div : Sup (s / t) = Sup s / Inf t :=
by simp_rw [div_eq_mul_inv, Sup_mul, Sup_inv]

@[to_additive] lemma Inf_div : Inf (s / t) = Inf s / Sup t :=
by simp_rw [div_eq_mul_inv, Inf_mul, Inf_inv]

end group
end complete_lattice

namespace linear_ordered_field

variables {K : Type*} [linear_ordered_field K] {a b r : K} (hr : 0 < r)
open set

include hr

lemma smul_Ioo : r • Ioo a b = Ioo (r • a) (r • b) :=
begin
  ext x,
  simp only [mem_smul_set, smul_eq_mul, mem_Ioo],
  split,
  { rintro ⟨a, ⟨a_h_left_left, a_h_left_right⟩, rfl⟩, split,
    exact (mul_lt_mul_left hr).mpr a_h_left_left,
    exact (mul_lt_mul_left hr).mpr a_h_left_right, },
  { rintro ⟨a_left, a_right⟩,
    use x / r,
    refine ⟨⟨(lt_div_iff' hr).mpr a_left, (div_lt_iff' hr).mpr a_right⟩, _⟩,
    rw mul_div_cancel' _ (ne_of_gt hr), }
end

lemma smul_Icc : r • Icc a b = Icc (r • a) (r • b) :=
begin
  ext x,
  simp only [mem_smul_set, smul_eq_mul, mem_Icc],
  split,
  { rintro ⟨a, ⟨a_h_left_left, a_h_left_right⟩, rfl⟩, split,
    exact (mul_le_mul_left hr).mpr a_h_left_left,
    exact (mul_le_mul_left hr).mpr a_h_left_right, },
  { rintro ⟨a_left, a_right⟩,
    use x / r,
    refine ⟨⟨(le_div_iff' hr).mpr a_left, (div_le_iff' hr).mpr a_right⟩, _⟩,
    rw mul_div_cancel' _ (ne_of_gt hr), }
end

lemma smul_Ico : r • Ico a b = Ico (r • a) (r • b) :=
begin
  ext x,
  simp only [mem_smul_set, smul_eq_mul, mem_Ico],
  split,
  { rintro ⟨a, ⟨a_h_left_left, a_h_left_right⟩, rfl⟩, split,
    exact (mul_le_mul_left hr).mpr a_h_left_left,
    exact (mul_lt_mul_left hr).mpr a_h_left_right, },
  { rintro ⟨a_left, a_right⟩,
    use x / r,
    refine ⟨⟨(le_div_iff' hr).mpr a_left, (div_lt_iff' hr).mpr a_right⟩, _⟩,
    rw mul_div_cancel' _ (ne_of_gt hr), }
end

lemma smul_Ioc : r • Ioc a b = Ioc (r • a) (r • b) :=
begin
  ext x,
  simp only [mem_smul_set, smul_eq_mul, mem_Ioc],
  split,
  { rintro ⟨a, ⟨a_h_left_left, a_h_left_right⟩, rfl⟩, split,
    exact (mul_lt_mul_left hr).mpr a_h_left_left,
    exact (mul_le_mul_left hr).mpr a_h_left_right, },
  { rintro ⟨a_left, a_right⟩,
    use x / r,
    refine ⟨⟨(lt_div_iff' hr).mpr a_left, (div_le_iff' hr).mpr a_right⟩, _⟩,
    rw mul_div_cancel' _ (ne_of_gt hr), }
end

lemma smul_Ioi : r • Ioi a = Ioi (r • a) :=
begin
  ext x,
  simp only [mem_smul_set, smul_eq_mul, mem_Ioi],
  split,
  { rintro ⟨a_w, a_h_left, rfl⟩,
    exact (mul_lt_mul_left hr).mpr a_h_left, },
  { rintro h,
    use x / r,
    split,
    exact (lt_div_iff' hr).mpr h,
    exact mul_div_cancel' _ (ne_of_gt hr), }
end

lemma smul_Iio : r • Iio a = Iio (r • a) :=
begin
  ext x,
  simp only [mem_smul_set, smul_eq_mul, mem_Iio],
  split,
  { rintro ⟨a_w, a_h_left, rfl⟩,
    exact (mul_lt_mul_left hr).mpr a_h_left, },
  { rintro h,
    use x / r,
    split,
    exact (div_lt_iff' hr).mpr h,
    exact mul_div_cancel' _ (ne_of_gt hr), }
end

lemma smul_Ici : r • Ici a = Ici (r • a) :=
begin
  ext x,
  simp only [mem_smul_set, smul_eq_mul, mem_Ioi],
  split,
  { rintro ⟨a_w, a_h_left, rfl⟩,
    exact (mul_le_mul_left hr).mpr a_h_left, },
  { rintro h,
    use x / r,
    split,
    exact (le_div_iff' hr).mpr h,
    exact mul_div_cancel' _ (ne_of_gt hr), }
end

lemma smul_Iic : r • Iic a = Iic (r • a) :=
begin
  ext x,
  simp only [mem_smul_set, smul_eq_mul, mem_Iio],
  split,
  { rintro ⟨a_w, a_h_left, rfl⟩,
    exact (mul_le_mul_left hr).mpr a_h_left, },
  { rintro h,
    use x / r,
    split,
    exact (div_le_iff' hr).mpr h,
    exact mul_div_cancel' _ (ne_of_gt hr), }
end
end linear_ordered_field


import topology.subset_properties

variables (X Y : Type) [topological_space X] [topological_space Y] (f : X → Y)

open filter set

open_locale filter -- for 𝓟
open_locale topological_space -- for 𝓝

/-

## Neighbourhood filters

If `α` is a topological space and `a : α` then `𝓝 a` is the following
filter on `α`: `X ∈ 𝓝 a` if and only if `X` contains an open neighbourhood
of `a`, or equivalently if `a` is in the interior of `X`. You should think
of `𝓝 a` as the "generalised subset" of `X` corresponding to an infinitesimally
small open neighbourhood of `a`.

Let's use the API for interior and closure, and check that `𝓝 a` is a filter.

Useful stuff from the topological space API:
`interior_univ : interior univ = univ`
`mem_univ x : x ∈ univ`
`interior_mono : s ⊆ t → interior s ⊆ interior t`
and guess what this is called:
`??? : interior (s ∩ t) = interior s ∩ interior t`
If you don't know how to #check your guess, ask me!
-/

variables {α : Type*} [topological_space α]

open set

-- neighbourhood filter 𝓝 a on α
example (a : α): filter α :=
{ sets := {X : set α | a ∈ interior X},
  univ_sets := begin
    rw mem_set_of_eq,
    rw interior_univ,
    exact mem_univ a,
  end,
  sets_of_superset := begin
    intros X Y haX hXY,
    rw mem_set_of_eq at *,
    exact interior_mono hXY haX,
  end,
  inter_sets := begin
    intros X Y hX hY,
    rw mem_set_of_eq at *,
    rw interior_inter,
    exact ⟨hX, hY⟩
  end }

/-

## Cluster points.

A cluster point `a : α` of a filter `F : filter α` on a topological
space should be thought of as a point in the closure of the "generalised set"
corresponding to `F`. Here's the formal definition.

A cluster point of a filter `F : filter α` (also known as an accumulation
point or a limit point) is `x : α` such that `𝓝 x ⊓ F ≠ ⊥`. The picture
is that the intersection of the generalised set `F` and the infinitesimal
open neighbourhood `𝓝 x` of `x` have nonempty intersection. Let's go through
the notation more carefully though. Recall that the order on filters is
upside-down, so `𝓝 x ⊓ F` means the filter generated by `F` and the
neighbourhoods of `x`, and `⊥` is the filter which contains every subset.
So this boils down to saying that there do not exists sets `A ∈ 𝓝 x` and `B ∈ F`
such that `A ∩ B = ∅`, or, in other words, every element of the filter
intersects every neighbourhood of `x`. To give an example, if `S` is any subset
of `α` then the cluster points of `𝓟 S` are just the points `x` such that any
open set containing `x` meets `S`, or equivalently that `x` is in the
closure of `S`.

The below lemma is called `cluster_pt.mono` in mathlib. The picture is
that if `F` and `G` are generalised subsets of a topological space
and `F ⊆ G`, then `closure F ⊆ closure G`. You can prove it by
using `cluster_pt.mono` of course, but why not give a direct proof
yourself? Start by rewriting `cluster_pt_iff`, which translates down
the definition of `cluster_pt` to a purely set-theoretic one not
involving `⊓`.

-/

example {x : α} {F G : filter α} (hxF : cluster_pt x F) (hFG : F ≤ G) :
  cluster_pt x G :=
begin
  rw cluster_pt_iff at hxF ⊢,
  intros,
  apply hxF hU,
  rw filter.le_def at hFG,
  apply hFG _ hV,
end

/-

## Compactness

The actual definition of `is_compact` in mathlib: A subset `S` of a topological
space `α` is *compact* if for every filter `F ≠ ⊥` such that `S ∈ F`,
there exists `a : α` such that every set of `F` also meets every
neighborhood of `a`. In pictures -- `S` is compact iff the closure of every
non-empty generalised subset `F ⊆ S` contains an element of `S`. I'm not
too sure this picture helps much (at least, it doesn't help me too much).
Somehow noncompactness results in generalised subsets which are
"on the boundary of `S`" but which don't intersect a small neighbourhood of
any elements of `S`. Perhaps it's best not to worry about this exotic
definition right now -- or perhaps one of you can explain it to me. Of course
it's equivalent to the usual definition of compactness, but we won't prove this.

So here is another proof that a closed subset of a compact space is compact.
As before, we prove the more general assertion that if `α` is any topological
space then the intersection of a compact subset of `α` and a closed subset
of `α` is a compact subset of `α`.

Here's the actual definition in mathlib:

`def is_compact (s : set α) := ∀ ⦃f⦄ [ne_bot f], f ≤ 𝓟 s → ∃a∈s, cluster_pt a f`

Note that `ne_bot f` is in square brackets, which means that the type
class inference system is supposed to supply it. We will explicitly
add this fact into the type class inference system with `haveI` below.

Now here's a suggestion for a proof.
First show that by compactness of `s`, we can find a cluster point
`a` for `f` in `s`.
Now show that this cluster point is in `t` too,
because `t` is closed. You might find

`is_closed.closure_eq : is_closed t → closure t = t`

useful, and also

`mem_closure_iff_cluster_pt : a ∈ closure S ↔ cluster_pt a (𝓟 S)`

-/

lemma closed_of_compact (s : set X) (hs : is_compact s)
  (t : set X) (ht : is_closed t) : is_compact (s ∩ t) :=
begin
  -- assume f is a filter which is not bot, and is ≤ the principal
  -- filter on s ∩ t (i.e. which contains `s ∩ t`). We need to find a
  -- cluster point for f which is in s ∩ t.
  intros f hnf hstf,
  -- Let's tell the type class inference system about `hnf : f.ne_bot`
  haveI := hnf,
  -- see if you can take it from here.
  obtain ⟨a, has, ha⟩ : ∃ a ∈ s, cluster_pt a f,
  { apply hs,
    apply le_trans hstf,
    rw principal_mono,
    apply inter_subset_left },
  -- we claim that this cluster point is in `t` too.
  have hat : a ∈ t,
  { -- It suffices to prove a ∈ closure t
    rw ← ht.closure_eq,
    -- or equivalently that `a` is a cluster point for `𝓟 t`
    rw mem_closure_iff_cluster_pt,
    -- but `a` is a cluster point for `f`
    -- so by cluster_pt.mono
    apply cluster_pt.mono ha,
    -- it suffices to prove f ≤ 𝓟 t.
    -- but f ≤ 𝓟 (s ∩ t) so this is now straightforward
    refine le_trans hstf _,
    rw principal_mono,
    apply inter_subset_right },
  -- so this `a` works
  exact ⟨a, ⟨has, hat⟩, ha⟩
end

-- stuff I never got to

/-
lemma comap_inf_principal_ne_bot_of_image_mem {α β : Type*}
  {f : filter β} {m : α → β}
  (hf : ne_bot f) {s : set α} (hs : m '' s ∈ f) :
  ne_bot (comap m f ⊓ 𝓟 s) :=
begin
  refine compl_compl s ▸ mt mem_sets_of_eq_bot _,
  rintros ⟨t, ht, hts⟩,
  rcases hf.nonempty_of_mem (inter_mem_sets hs ht) with ⟨_, ⟨x, hxs, rfl⟩, hxt⟩,
  exact absurd hxs (hts hxt)
end

lemma image_compact_of_compact (hf : continuous f) (s : set X)
  (hs : is_compact s) :
  is_compact (f '' s) :=
begin
  intros l lne ls,
  have : ne_bot (l.comap f ⊓ 𝓟 s) :=
    comap_inf_principal_ne_bot_of_image_mem lne (le_principal_iff.1 ls),
  obtain ⟨a, has, ha⟩ : ∃ a ∈ s, cluster_pt a (l.comap f ⊓ 𝓟 s) := @@hs this inf_le_right,
  use [f a, mem_image_of_mem f has],
  have : tendsto f (𝓝 a ⊓ (comap f l ⊓ 𝓟 s)) (𝓝 (f a) ⊓ l),
  { have this1 : continuous_within_at f s a := continuous.continuous_within_at hf,
    have this2 := this1.inf,
    have this3 := this2 (@tendsto_comap _ _ f l),
    convert this3 using 1,
    rw nhds_within,
    ac_refl },
  exact @@tendsto.ne_bot _ this ha,
end

-/