Andrew Sneap - 27th April 2021
Updated July 2022

In this file I define common divisors, and HCF's, along with a proof
that the Euclidean Algorithm produces HCF's.
\begin{code}

{-# OPTIONS --safe --without-K #-}

open import MLTT.Spartan renaming (_+_ to _∔_)

open import Naturals.Addition
open import Naturals.Division
open import Naturals.Multiplication
open import Naturals.Order
open import Naturals.Properties
open import Notation.Order
open import UF.DiscreteAndSeparated
open import UF.FunExt
open import UF.Subsingletons
open import UF.Subsingletons-FunExt

module Naturals.HCF where

\end{code}

A common divisor d of x and y is a Natural Number which divides x and y,
and clearly is a proposition.

\begin{code}

is-common-divisor : (d x y : )  𝓤₀ ̇
is-common-divisor d x y = (d  x) × (d  y)

is-common-divisor-is-prop : (d x y : )
                           is-prop (is-common-divisor (succ d) x y)
is-common-divisor-is-prop d x y = ×-is-prop (_∣_-is-prop d x) (_∣_-is-prop d y)

\end{code}

The highest common divisor of x and y is the common divisor of x and y
which is greater than all other common divisors. One way to formulate
the type of the hcf h of x and y is to say that any other common
factor is a divisor of the highest common factor.

\begin{code}

is-hcf : (h x y : )  𝓤₀ ̇
is-hcf h x y = (is-common-divisor h x y)
             × ((d : )  is-common-divisor d x y  d  h)

\end{code}

Of course, any we can retrieve from the cartesian product that the hcf
is a common divisor.

\begin{code}

is-hcf-gives-is-common-divisor : (h x y : )
                                is-hcf h x y
                                is-common-divisor h x y
is-hcf-gives-is-common-divisor h x y (a , p) = a

\end{code}

The statement "succ h is the highest common factor of x and y" is a
proposition.  In order to prove this, function extensionality is
required, because the second projection of the cartesian product is a
function. With function extensionality, proof that this statement is a
proposition follows from the proof that (is-common-divisor d x y) is a
proposition.

\begin{code}

is-hcf-is-prop : Fun-Ext  (h x y : )  is-prop (is-hcf (succ h) x y)
is-hcf-is-prop fe h x y p q = ×-is-prop (is-common-divisor-is-prop h x y) II p q
  where
    I : (d : )  is-common-divisor d x y  is-prop (d  succ h)
    I 0        i x = 𝟘-elim (zero-does-not-divide-positive h x)
    I (succ d) i   = d  (succ h) -is-prop

    II : is-prop ((d : )  is-common-divisor d x y  d  succ h)
    II p' q' = Π₂-is-prop fe I p' q'

\end{code}

Of course, hcf is commutative, which is easily proved by re-ordering
projections, and other properties of hcf are simple corollaries of the
definition.

\begin{code}

hcf-comm : (x y h : )  is-hcf h x y  is-hcf h y x
hcf-comm x y h ((h∣x , h∣y) , f) = (h∣y , h∣x) , γ
 where
  γ : (d : )  is-common-divisor d y x  d  h
  γ d (d∣y , d∣x) = f d (d∣x , d∣y)

hcf-comm' : (x y : )  Σ h   , is-hcf h x y  Σ h   , is-hcf h y x
hcf-comm' x y (h , is-hcf) = h , (hcf-comm x y h is-hcf)

hcf-one-left : {x : }  is-hcf 1 1 x
hcf-one-left {x} = (∣-refl , 1-divides-all x) , γ
 where
  γ : (d : )  is-common-divisor d 1 x  d  1
  γ d (d-divides-1 , _) = d-divides-1

hcf-one-right : {x : }  is-hcf 1 x 1
hcf-one-right {x} = hcf-comm 1 x 1 hcf-one-left

hcf-refl : {x : }  is-hcf x x x
hcf-refl {x} = (∣-refl , ∣-refl) , γ
 where
  γ : (d : )  is-common-divisor d x x  d  x
  γ d (d-divides-x , _) = d-divides-x

hcf-zero-left : {x : }  is-hcf x 0 x
hcf-zero-left {x} = (everything-divides-zero , ∣-refl) , γ
 where
  γ : (d : )  is-common-divisor d 0 x  d  x
  γ d (_ , d-divides-0) = d-divides-0

hcf-zero-right : {x : }  is-hcf x x 0
hcf-zero-right {x} = hcf-comm 0 x x hcf-zero-left

\end{code}

With an eye towards implement Euclid's algorithm to compute the
highest common factor, we now prove two lemmas; each direction of the
following proof:

If x = q * y + r, then is-hcf h x y ↔ is-hcf y r.

For Euclid's algorithm, we only need the right-to-left implication,
but both are proved for completeness.

The general idea of the right-to-left implication is as follows:

x = q * y + r, h | y and h | r, with h = hcf(y , r).

Now, clearly h | x since h | (q * y + r), and h | y by assumption,
so h is a common factor of x and y.

To show that h is the highest common factor, assume that d | x,
d | y, and further that d * u = x , d * v = y for some u , v.

If we can show that d | y, and d | r, then d | h since is-hcf h y r.
First, d | y by assumption.

Now, d * u = q * (d * v) + r, so by the factor-of-sum-consequence,
d | r, and we are done.

\begin{code}

euclids-algorithm-lemma : (x y q r h : )
                         x  q * y + r
                         is-hcf h x y
                         is-hcf h y r
euclids-algorithm-lemma x y q r h e (((a , e₀) , b , e₁) , f) = I , II
 where
  I : is-common-divisor h y r
  I = (b , e₁) , factor-of-sum-consequence h a (q * b) r i
   where
    i : h * a  h * (q * b) + r
    i = h * a           =⟨ e₀                                            
        x               =⟨ e                                             
        q * y + r       =⟨ ap  -  q * - + r) (e₁ ⁻¹)                  
        q * (h * b) + r =⟨ ap (_+ r) (mult-associativity q h b ⁻¹)       
        q * h * b + r   =⟨ ap  -  - * b + r) (mult-commutativity q h) 
        h * q * b + r   =⟨ ap (_+ r) (mult-associativity h q b)          
        h * (q * b) + r 

  II : (d : )  is-common-divisor d y r  d  h
  II d ((u , e₁) , v , e₂) = f d ((q * u + v , i) , u , e₁)
   where
    i : d * (q * u + v)  x
    i = d * (q * u + v)     =⟨ distributivity-mult-over-addition d (q * u) v 
        d * (q * u) + d * v =⟨ ap (d * (q * u) +_) e₂                        
        d * (q * u) + r     =⟨ ap (_+ r) (mult-associativity d q u ⁻¹)       
        d * q * u + r       =⟨ ap  -  - * u + r) (mult-commutativity d q) 
        q * d * u + r       =⟨ ap (_+ r) (mult-associativity q d u)          
        q * (d * u) + r     =⟨ ap  -  q * - + r) e₁                       
        q * y + r           =⟨ e ⁻¹                                          
        x                   

euclids-algorithm-lemma' : (x y q r h : )
                          x  q * y + r
                          is-hcf h y r
                          is-hcf h x y
euclids-algorithm-lemma' x y q r h e (((a , e₀) , b , e₁) , f) = I , II
 where
  I : is-common-divisor h x y
  I = (q * a + b , i) , (a , e₀)
   where
    i : h * (q * a + b)  x
    i = h * (q * a + b)     =⟨ distributivity-mult-over-addition h (q * a) b 
        h * (q * a) + h * b =⟨ ap (h * (q * a) +_) e₁                        
        h * (q * a) + r     =⟨ ap (_+ r) (mult-associativity h q a ⁻¹)       
        h * q * a + r       =⟨ ap  -  - * a + r) (mult-commutativity h q) 
        q * h * a + r       =⟨ ap (_+ r) (mult-associativity q h a)          
        q * (h * a) + r     =⟨ ap  -  q * - + r) e₀                       
        q * y + r           =⟨ e ⁻¹                                          
        x                   
  II : (d : )  is-common-divisor d x y  d  h
  II d ((u , e₂) , v , e₃)  = f d ((v , e₃) , ii)
   where
    i : d * u  d * (q * v) + r
    i = d * u           =⟨ e₂                                            
        x               =⟨ e                                             
        q * y + r       =⟨ ap  -  q * - + r) (e₃ ⁻¹)                  
        q * (d * v) + r =⟨ ap (_+ r) (mult-associativity q d v ⁻¹)       
        q * d * v + r   =⟨ ap  -  - * v + r) (mult-commutativity q d) 
        d * q * v + r   =⟨ ap (_+ r) (mult-associativity d q v)          
        d * (q * v) + r 

    ii : d  r
    ii = factor-of-sum-consequence d u (q * v) r i


\end{code}

Now we have the function which computes the highest common factor for any two
natural numbers x and y.  This function uses course-of-values induction in order
to satisfy the Agda termination checker.

The step function includes an induction, which says the following:

If for any number x, we can find a number r with r < x, and for any number k
there exists a highest common factor of r and k, then for any y there exists a
highest common factor of x and y.

\begin{code}

HCF : (x y : )  Σ h   , is-hcf h x y
HCF = course-of-values-induction  x  (y : )  Σ h   , is-hcf h x y) step
 where
  step : (x : )
        ((r : )  r < x  (y : )  Σ h   , is-hcf h r y)
        (y : )
        Σ h   , is-hcf h x y
  step 0        IH y = y , (everything-divides-zero , ∣-refl) , γ
   where
    γ : (d : )  is-common-divisor d 0 y  d  y
    γ d (a , b) = b
  step (succ x) IH y = I (division y x)
   where
    I : Σ q   , Σ r   , (y  q * succ x + r) × (r < succ x)
       Σ h   , is-hcf h (succ x) y
    I (q , r , e₀ , l) = II (IH r l (succ x))
     where
      II : Σ h   , is-hcf h r (succ x)  Σ h   , is-hcf h (succ x) y
      II (h , h-is-hcf) = h , hcf-comm y (succ x) h ii
       where
        i : is-hcf h (succ x) r
        i = hcf-comm r (succ x) h h-is-hcf

        ii : is-hcf h y (succ x)
        ii = euclids-algorithm-lemma' y (succ x) q r h e₀ i

hcf : (x y : )  
hcf x y = pr₁ (HCF x y)

hcf-is-HCF : (x y : )  is-hcf (hcf x y) x y
hcf-is-HCF x y = pr₂ (HCF x y)

\end{code}

Two numbers being coprime is also a proposition, as a simple
consequence of hcf being a proposition for all values of h.

Two numbers are coprime in the special case that the hcf is 1.

\begin{code}

coprime' :     𝓤₀ ̇
coprime' x y = hcf x y  1

coprime'-is-prop : (x y : )  is-prop (coprime' x y)
coprime'-is-prop _ _ = ℕ-is-set

coprime : (a b : )  𝓤₀ ̇
coprime a b = is-hcf 1 a b

coprime-is-prop : Fun-Ext  (a b : )  is-prop (coprime a b)
coprime-is-prop fe a b = is-hcf-is-prop fe 0 a b

coprime'-to-coprime : (x y : )  coprime' x y  coprime x y
coprime'-to-coprime x y p = transport  -  is-hcf - x y) p (hcf-is-HCF x y)

coprime-0-1 : coprime 0 1
coprime-0-1 = (1-divides-all 0 , 1-divides-all 1) , γ
 where
  γ : (d : )  is-common-divisor d 0 1  d  1
  γ d (_ , d-divides-one) = d-divides-one

divbyhcf' : (a b : )
           Σ h   , Σ x   , Σ y   , ((h * x  a) × (h * y  b))
                                        × coprime x y
divbyhcf' 0 b = b , 0 , 1 , (refl , refl) , coprime-0-1
divbyhcf' (succ a) b = γ' (HCF (succ a) b)
 where
  γ' : Σ h   , is-hcf h (succ a) b
      Σ h   , Σ x   , Σ y   , ((h * x  succ a) × (h * y  b))
                                   × coprime x y
  γ' (0 , (p , _) , τ) = 𝟘-elim (zero-does-not-divide-positive a p)
  γ' (succ h , ((x , α) , (y , β)) , τ) = succ h , x , y , (α , β) , γ
   where
    γ₁ : is-common-divisor 1 x y
    γ₁ = 1-divides-all x , 1-divides-all y

    γ₂ : (d : )  is-common-divisor d x y  d  1
    γ₂ d ((k , δ) , (l , ψ)) = division-refl-right-factor h d II
     where
      I : (k x a : )
         d * k  x
         succ h * x  a
         succ h * d  a
      I k x a e₁ e₂ = k , (succ h * d * k  =⟨ mult-associativity (succ h) d k 
                          succ h * (d * k) =⟨ ap (succ h *_) e₁               
                          succ h * x       =⟨ e₂                              
                          a )

      II : (succ h * d)  succ h
      II = τ (succ h * d) (I k x (succ a) δ α , I l y b ψ β)

    γ : coprime x y
    γ = γ₁ , γ₂

divbyhcf : (a b : )
          Σ h   , Σ x   , Σ y   , ((h * x  a)
                                       × (h * y  b))
                                       × coprime x y
divbyhcf 0 b = b , 0 , 1 , I , II , III
  where
  I : (b * 0  zero) × (b * 1  b)
  I = refl , refl
  II : (1  0) × (1  1)
  II = everything-divides-zero , 1-divides-all 1
  III : (d : )  is-common-divisor d 0 1  d  1
  III d (_ , d-divides-one) = d-divides-one
divbyhcf (succ a) b = I (HCF (succ a) b)
 where
  I : Σ c   , is-hcf c (succ a) b
     Σ h   , Σ x   , Σ y   , ((h * x  succ a)
                                  × (h * y  b))
                                  × coprime x y
  I (0 , ((x , xₚ) , y , yₚ) , τ) = 𝟘-elim (positive-not-zero a II)
   where
    II : succ a  0
    II = succ a  =⟨ xₚ ⁻¹                     
         0 * x   =⟨ mult-commutativity zero x 
         0       
  I (succ h , ((x , xₚ) , y , yₚ) , τ) = succ h , x , y , (xₚ , yₚ) , goal
   where
    II : (f' : )  is-common-divisor f' x y  f'  1
    II f' ((α , αₚ) , β , βₚ) = III (τ (succ h * f') ((α , αₚ') , β , βₚ'))
     where
      αₚ' : succ h * f' * α  succ a
      αₚ' = succ h * f' * α     =⟨ mult-associativity (succ h) f' α 
            succ h * (f' * α)   =⟨ ap (succ h *_) αₚ                
            succ h * x          =⟨ xₚ                               
            succ a              

      βₚ' : succ h * f' * β  b
      βₚ' = succ h * f' * β   =⟨ mult-associativity (succ h) f' β 
            succ h * (f' * β) =⟨ ap (succ h *_) βₚ                
            succ h * y        =⟨ yₚ                               
            b                 

      III : (succ h) * f'  (succ h)  f'  1
      III (δ , δₚ) = 1 , left-factor-one f' δ γ
       where
        e : succ h * (f' * δ)  succ h * 1
        e = succ h * (f' * δ) =⟨ mult-associativity (succ h) f' δ ⁻¹ 
            succ h * f' * δ   =⟨ δₚ 
            succ h            

        γ : f' * δ  1
        γ = mult-left-cancellable (f' * δ) 1 h e

    goal : coprime x y
    goal = (1-divides-all x , 1-divides-all y) , II

hcf-unique : (a b : )
            ((h , p) : Σ h   , is-hcf h a b)
            ((h' , p') : Σ h'   , is-hcf h' a b)
            h  h'
hcf-unique a b (h , h-icd , f) (h' , h'-icd , f') = ∣-anti h h' I II
 where
  I : h  h'
  I = f' h h-icd

  II : h'  h
  II = f h' h'-icd

coprime-to-coprime' : (x y : )  coprime x y  coprime' x y
coprime-to-coprime' x y p = γ
 where
  I : is-hcf (hcf x y) x y
  I = hcf-is-HCF x y

  γ : hcf x y  1
  γ = hcf-unique x y (hcf x y , I) (1 , p)

\end{code}

The statement "x and y have a highest-common-factor" is also a
proposition. Again, function extensionality is required.

To prove that the hcf is unique, we assume there are two different
hcf's. But by the definition of is-hcf, all common factors are factors
of the hcf, and both hcf's are common factors. Two numbers which are
factors of each other are equal by the anti-symmetric property of
division.

\begin{code}

has-hcf : (x y : )  𝓤₀ ̇
has-hcf x y = Σ d   , is-hcf (succ d) x y

has-hcf-is-prop : Fun-Ext  (x y : )  is-prop (has-hcf x y)
has-hcf-is-prop fe x y (h₁ , h₁-hcf) (h₂ , h₂-hcf) = to-subtype-= γ₁ γ₂
 where
  γ₁ : (d : )  is-prop (is-hcf (succ d) x y)
  γ₁ d = is-hcf-is-prop fe d x y

  I : succ h₁  succ h₂
  I = hcf-unique x y (succ h₁ , h₁-hcf) (succ h₂ , h₂-hcf)

  γ₂ : h₁  h₂
  γ₂ = succ-lc I

\end{code}