KLDivergence.lean
import Mathlib

set_option linter.style.emptyLine false

open Finset

section KLDivergence

-- Define our finite sample space X
variable {X : Type*} [Fintype X]

-- P and Q are our probability distributions
variable (P Q : X → ℝ)

-- We define log as a general function to mirror your axiomatic approach
variable (log : ℝ → ℝ)

-- Axiom 1: Shannon Entropy
def H (P : X → ℝ) (log : ℝ → ℝ) : ℝ :=
  - ∑ x, P x * log (P x)

-- Axiom 2: Cross Entropy
def H_cross (P Q : X → ℝ) (log : ℝ → ℝ) : ℝ :=
  - ∑ x, P x * log (Q x)

-- Axiom 3: KL Divergence
noncomputable def D_KL (P Q : X → ℝ) (log : ℝ → ℝ) : ℝ :=
  ∑ x, P x * log (P x / Q x)

-- Recall: Log Quotient Rule
axiom log_quotient (A B : ℝ) : log (A / B) = log A - log B

-- Goal: Prove the identity H(P, Q) = H(P) + D_KL(P||Q)
-- We follow the algebraic steps: D_KL(P||Q) = H(P,Q) - H(P)
theorem kl_identity : H_cross P Q log - H P log = D_KL P Q log := by
  -- Sub in Ax 1 & Ax 2
  rw [H_cross, H]

  -- D_KL(P||Q) = [-\sum P(x)log(Q(x))] - [-\sum P(x)log(P(x))]
  -- This rearranges to: -\sum P(x)log Q(x) + \sum P(x)log P(x)
  have step1 : (- ∑ x, P x * log (Q x)) - (- ∑ x, P x * log (P x))
             = (∑ x, P x * log (P x)) - (∑ x, P x * log (Q x)) := by ring
  rw [step1]

  -- "= \sum P(x)log P(x) - \sum P(x)log Q(x)"
  rw [← sum_sub_distrib]

  -- "= \sum P(x) [ log P(x) - log Q(x) ]"
  have step2 : (∑ x, (P x * log (P x) - P x * log (Q x)))
             = ∑ x, P x * (log (P x) - log (Q x)) := by
    congr 1
    ext x
    ring
  rw [step2]

  -- Use log quotient rule backward: log P(x) - log Q(x) = log(P(x)/Q(x))
  have step3 : (∑ x, P x * (log (P x) - log (Q x)))
             = ∑ x, P x * log (P x / Q x) := by
    congr 1
    ext x
    rw [← log_quotient]
  rw [step3]

  -- "= D_KL(P||Q)"
  -- The goal matches the definition of D_KL perfectly now.
  rfl

end KLDivergence