/- Copyright (c) 2026 Sovereign Research Stack. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Research Stack Team

DiffusionSNRBias.lean — SNR-t Bias Correction for Diffusion Probabilistic Models

This module formalizes the SNR-t bias phenomenon and differential correction
method from "Elucidating the SNR-t Bias of Diffusion Probabilistic Models"
(arXiv:2604.16044, 2026).

Key contributions from the paper:
1. SNR-t Bias: The actual SNR of predicted samples xHat_t in reverse process
   is always lower than that of perturbed sample x_t in forward process.
2. Differential Correction: Uses differential signal Δ_t = xHat_{t-1} - xTheta^0(xHat_t, t)
   to guide denoising toward ideal perturbed samples.

Per AGENTS.md §1.4: Uses Q16_16 fixed-point for hardware-native computation.
Per AGENTS.md §2: PascalCase types, camelCase functions.
Per AGENTS.md §4: All defs must have eval witnesses or theorems.

Reference: alphaXiv.org/abs/2604.16044
-/

import Mathlib.Data.Nat.Basic
import Mathlib.Data.Real.Basic
import Mathlib.Data.Fin.Basic

namespace Semantics.DiffusionSNRBias

-- ════════════════════════════════════════════════════════════
-- §0  Fixed-Point Precision (Q16.16 for diffusion scores)
-- ════════════════════════════════════════════════════════════

/-- Q16.16 fixed-point for SNR computations. -/
structure Q1616 where
  raw : Int
  deriving Repr, DecidableEq, Inhabited, BEq

namespace Q1616

def zero    : Q1616 := ⟨0⟩
def one     : Q1616 := ⟨65536⟩        -- 0x00010000 = 1.0
def epsilon : Q1616 := ⟨1⟩            -- 2^{-16}

def ofNat (n : Nat) : Q1616 := ⟨n * 65536⟩  -- Integer to Q16.16

def toFloat (q : Q1616) : Float := (Float.ofInt q.raw) / 65536.0

def add (a b : Q1616) : Q1616 := ⟨a.raw + b.raw⟩
def sub (a b : Q1616) : Q1616 := ⟨a.raw - b.raw⟩
def mul (a b : Q1616) : Q1616 := ⟨(a.raw * b.raw) / 65536⟩
def div (a b : Q1616) : Q1616 := ⟨(a.raw * 65536) / b.raw⟩

instance : Add Q1616 := ⟨add⟩
instance : Sub Q1616 := ⟨sub⟩
instance : Mul Q1616 := ⟨mul⟩
instance : Div Q1616 := ⟨div⟩
instance : Neg Q1616 := ⟨fun a => ⟨-a.raw⟩⟩

instance : LE Q1616 := ⟨fun a b => a.raw ≤ b.raw⟩
instance : LT Q1616 := ⟨fun a b => a.raw < b.raw⟩

/-- Square root via Newton-Raphson (seeded). -/
def sqrt (x : Q1616) : Q1616 :=
  if x.raw ≤ 0 then zero
  else
    -- 3 iterations of Newton-Raphson
    let seed := ⟨65536⟩  -- Initial guess = 1.0
    let iter1 := (seed + x / seed) / ofNat 2
    let iter2 := (iter1 + x / iter1) / ofNat 2
    let iter3 := (iter2 + x / iter2) / ofNat 2
    iter3

/-- Clip value to [lo, hi] range. -/
def clip (x lo hi : Q1616) : Q1616 :=
  if x < lo then lo
  else if x > hi then hi
  else x

end Q1616

-- ════════════════════════════════════════════════════════════
-- §1  Diffusion Process Definitions
-- ════════════════════════════════════════════════════════════

/-- Timestep in diffusion process (T down to 0). -/
abbrev Timestep := Nat

/-- Image/tensor dimensions (H × W × C). -/
structure ImageShape where
  height : Nat
  width : Nat
  channels : Nat
  deriving Repr, Inhabited

/-- Noised sample x_t at timestep t. -/
structure PerturbedSample (shape : ImageShape) where
  data : Array Q1616  -- Flattened tensor
  timestep : Timestep
  wf : data.size = shape.height * shape.width * shape.channels
  deriving Repr

/-- Predicted sample xHat_t from reverse process. -/
structure PredictedSample (shape : ImageShape) where
  data : Array Q1616
  timestep : Timestep
  wf : data.size = shape.height * shape.width * shape.channels
  deriving Repr

/-- Reconstructed sample xTheta^0(x_t, t) = predicted x_0. -/
structure ReconstructedSample (shape : ImageShape) where
  data : Array Q1616
  timestep : Timestep
  wf : data.size = shape.height * shape.width * shape.channels
  deriving Repr

/-- Noise prediction ε_θ(x_t, t). -/
structure NoisePrediction (shape : ImageShape) where
  data : Array Q1616
  timestep : Timestep
  wf : data.size = shape.height * shape.width * shape.channels
  deriving Repr

-- ════════════════════════════════════════════════════════════
-- §2  Signal-to-Noise Ratio (SNR) Computation
-- ════════════════════════════════════════════════════════════

/-- Compute mean squared norm ||x||²_2. -/
def meanSquaredNorm (x : Array Q1616) : Q1616 :=
  let sqSum := x.foldl (fun acc v => acc + (v * v)) Q1616.zero
  sqSum / Q1616.ofNat x.size

/-- SNR of a sample: ratio of signal power to noise power.
    For diffusion: SNR(t) ≈ α_t² / σ_t² -/
structure SNR where
  value : Q1616  -- Signal-to-noise ratio
  logSNR : Q1616  -- log(SNR) for stability
  deriving Repr, Inhabited

namespace SNR

/-- Compute SNR from mean squared norms. -/
def fromSignalNoise (signal : Q1616) (noise : Q1616) : SNR :=
  let snr := if noise.raw = 0 then Q1616.ofNat 1000 else signal / noise
  { value := snr
    logSNR := Q1616.ofNat 0 }  -- Placeholder for log

/-- Compare SNR values (paper finding: SNR_reverse < SNR_forward). -/
def lessThan (a b : SNR) : Bool := a.value < b.value

instance : LT SNR := ⟨fun a b => a.value < b.value⟩

end SNR

-- ════════════════════════════════════════════════════════════
-- §3  SNR-t Bias Phenomenon (Paper Section 4)
-- ════════════════════════════════════════════════════════════

/-- SNR-t Bias: The mismatch between predicted sample SNR and timestep SNR.
    
    Paper Key Finding 1:
    The network produces significantly inaccurate predictions when processing
    samples with mismatched SNR and timesteps.
    
    Key Finding 2:
    The actual SNR of xHat_t in reverse process is always lower than x_t at
    the same timestep t in forward process.
-/ 
structure SNRTBias (shape : ImageShape) where
  -- Forward perturbed sample at timestep t
  forwardSample : PerturbedSample shape
  -- Reverse predicted sample at same timestep t
  reverseSample : PredictedSample shape
  -- SNR values
  forwardSNR : SNR
  reverseSNR : SNR
  -- Bias indicator: reverseSNR.value < forwardSNR.value
  biasExists : Bool
  deriving Repr

namespace SNRTBias

/-- Detect if SNR-t bias exists (paper's experimental finding). -/
def detectBias {shape : ImageShape}
    (x_t : PerturbedSample shape) (xHat_t : PredictedSample shape) : SNRTBias shape :=
  let signalFwd := meanSquaredNorm x_t.data
  let signalRev := meanSquaredNorm xHat_t.data
  let snrFwd := SNR.fromSignalNoise signalFwd (Q1616.ofNat 1)
  let snrRev := SNR.fromSignalNoise signalRev (Q1616.ofNat 1)
  { forwardSample := x_t
    reverseSample := xHat_t
    forwardSNR := snrFwd
    reverseSNR := snrRev
    biasExists := SNR.lessThan snrRev snrFwd }

/-- Theorem: SNR-t bias always exists (paper's theoretical result).
    The actual SNR of xHat_t is always lower than SNR of x_t at same t. -/
theorem snrBiasAlwaysExists {shape : ImageShape} (bias : SNRTBias shape)
    (hValid : bias.forwardSample.timestep = bias.reverseSample.timestep) :
    bias.biasExists = true := by
  -- Paper proof: From Eq. 15, reverse process SNR = γ̂_t² / (φ_{t+1}² + ...)
  -- Forward SNR = α_t² / σ_t²
  -- Since γ̂_t < α_t (information loss during reconstruction), bias exists
  -- TODO(lean-port): UNPROVABLE AS STATED. The theorem claims bias always exists
  -- for any SNRTBias value, but detectBias computes bias from unconstrained samples.
  -- The forward/reverse samples could be constructed such that reverseSNR ≥ forwardSNR.
  -- Needs additional hypotheses: e.g., forwardSample is ground-truth perturbed,
  -- reverseSample is network-predicted, and a reconstruction-model bound on γ̂_t.
  sorry

end SNRTBias

-- ════════════════════════════════════════════════════════════
-- §4  Differential Correction Method (Paper Section 5.2)
-- ════════════════════════════════════════════════════════════

/-- Differential signal Δ_t = xHat_{t-1} - xTheta^0(xHat_t, t)
    
    This signal contains directional information pointing toward x_{t-1}.
    Paper Eq. 16: Contains gradient toward ideal perturbed sample.
-/
def differentialSignal {shape : ImageShape}
    (xHat_t_minus_1 : PredictedSample shape)
    (xTheta0 : ReconstructedSample shape) : Array Q1616 :=
  -- Element-wise subtraction: xHat_{t-1} - xTheta^0(xHat_t, t)
  Array.zipWith (fun a b => a - b) xHat_t_minus_1.data xTheta0.data

/-- Differential correction with guidance factor λ_t.
    
    Paper Eq. 17: 
    xHat_{t-1}^{corrected} = xHat_{t-1} + λ_t · Δ_t
    
    where λ_t adjusts magnitude of differential signal effect.
-/
def differentialCorrection {shape : ImageShape}
    (xHat_t_minus_1 : PredictedSample shape)
    (xTheta0 : ReconstructedSample shape)
    (lambda_t : Q1616)  -- Guidance factor (hyperparameter)
    : PredictedSample shape :=
  let delta := differentialSignal xHat_t_minus_1 xTheta0
  let correction := delta.map (fun d => lambda_t * d)
  let corrected := Array.zipWith (fun a c => a + c) xHat_t_minus_1.data correction
  { data := corrected
    timestep := xHat_t_minus_1.timestep
    wf := by
      have hShape : xHat_t_minus_1.data.size = xTheta0.data.size := by
        rw [xHat_t_minus_1.wf, xTheta0.wf]
      have h1 : (Array.zipWith (fun a b => a - b) xHat_t_minus_1.data xTheta0.data).size = xHat_t_minus_1.data.size := by
        rw [Array.size_zipWith]
        rw [hShape]
        simp
      have h2 : (Array.map (fun d => lambda_t * d) (Array.zipWith (fun a b => a - b) xHat_t_minus_1.data xTheta0.data)).size = xHat_t_minus_1.data.size := by
        rw [Array.size_map]
        exact h1
      have h3 : (Array.zipWith (fun a c => a + c) xHat_t_minus_1.data (Array.map (fun d => lambda_t * d) (Array.zipWith (fun a b => a - b) xHat_t_minus_1.data xTheta0.data))).size = xHat_t_minus_1.data.size := by
        rw [Array.size_zipWith]
        rw [h2]
        simp
      exact h3.trans xHat_t_minus_1.wf
  }

/-- Guidance factor strategy (paper Section 6.4 / Appendix D). -/
structure GuidanceStrategy (shape : ImageShape) where
  -- Linear schedule: λ_t decreases over timesteps
  linearSchedule : Timestep → Q1616
  -- Constant guidance: λ_t = λ for all t
  constantValue : Q1616
  -- Adaptive: based on estimated SNR mismatch
  adaptive : SNRTBias shape → Q1616

instance : Repr (GuidanceStrategy shape) where
  reprPrec _ _ := "<GuidanceStrategy>"

namespace GuidanceStrategy

/-- Default linear schedule: λ_t = λ_max · (1 - t/T). -/
def defaultLinear (shape : ImageShape) (maxLambda : Q1616) (totalSteps : Timestep) : GuidanceStrategy shape :=
  { linearSchedule := fun t => maxLambda * Q1616.ofNat (totalSteps - t) / Q1616.ofNat totalSteps
    constantValue := maxLambda
    adaptive := fun _ => maxLambda }

end GuidanceStrategy

-- ════════════════════════════════════════════════════════════
-- §5  Assumption 5.1: Reconstruction Model (Paper Section 5.1)
-- ════════════════════════════════════════════════════════════

/-- Paper Assumption 5.1: Reconstruction model formulation.
    
    xTheta^0(x_t, t) = γ_t · x_0 + φ_t · ε_t
    
    where:
    - 0 < γ_t ≤ 1 (energy/information loss during reconstruction)
    - φ_t < M (bounded noise coefficient)
    - ε_t ~ N(0, I)
-/
structure ReconstructionModel where
  gamma_t : Q1616  -- Data preservation coefficient (0 < γ_t ≤ 1)
  phi_t : Q1616    -- Noise coefficient (bounded)
  wf_gamma : gamma_t.raw > 0 ∧ gamma_t.raw ≤ 65536
  wf_phi : phi_t.raw < 6553600  -- Some large bound M
  deriving Repr

namespace ReconstructionModel

/-- Energy conservation check: ||xTheta^0||² ≤ ||x_0||² + φ_t². -/
def energyConservation (model : ReconstructionModel) (x0_norm : Q1616) : Bool :=
  -- Variance identity: E[||x||²] = ||x̄||² + Var(||x||)
  -- Non-negativity of variance implies energy constraint
  model.gamma_t ≤ Q1616.one

/-- Theorem 5.1: SNR of biased sample xHat_t.
    
    Paper Eq. 12:
    SNR(xHat_t) = γ̂_t² / (φ_{t+1}² + ψ_{t-1}²)
    
    where γ̂_t = γ_{t+1} · ψ_{t-1}. -/
theorem snrOfBiasedSample (model : ReconstructionModel)
    (gamma_hat : Q1616) (psi_t_minus_1 : Q1616) :
    let numerator := gamma_hat * gamma_hat
    let denominator := model.phi_t * model.phi_t + psi_t_minus_1 * psi_t_minus_1
    SNR.fromSignalNoise numerator denominator < SNR.fromSignalNoise model.gamma_t Q1616.one := by
  -- Proof: Since γ̂_t ≤ γ_t < 1 and φ_t > 0, SNR is reduced
  -- TODO(lean-port): UNPROVABLE AS STATED. Needs hypotheses linking gamma_hat to
  -- model.gamma_t (e.g., gamma_hat.raw ≤ model.gamma_t.raw) and non-zero denominator.
  -- SNR.fromSignalNoise uses conditional division (returns 1000 when noise=0),
  -- so the inequality also needs a case split on whether denominator.raw = 0.
  sorry

end ReconstructionModel

-- ════════════════════════════════════════════════════════════
-- §6  Correction Verification Metrics
-- ════════════════════════════════════════════════════════════

/-- Correction effectiveness metrics. -/
structure CorrectionMetrics where
  -- SNR improvement after correction
  snrImprovement : Q1616
  -- Noise prediction accuracy: ||ε_θ(xHat_t, t) - ε_t||
  noiseAccuracy : Q1616
  -- Sample quality: reduced artifacts / improved coherence
  qualityScore : Q1616
  deriving Repr, Inhabited

/-- Evaluate correction effectiveness. -/
def evaluateCorrection {shape : ImageShape}
    (before : PredictedSample shape)
    (after : PredictedSample shape)
    (target : PerturbedSample shape) : CorrectionMetrics :=
  let snrBefore := meanSquaredNorm before.data
  let snrAfter := meanSquaredNorm after.data
  let snrTarget := meanSquaredNorm target.data
  { snrImprovement := snrAfter - snrBefore
    noiseAccuracy := snrTarget - snrAfter  -- Distance to ideal
    qualityScore := Q1616.ofNat 0 }  -- Placeholder for perceptual metric

-- ════════════════════════════════════════════════════════════
-- §7  Integration with Ordered Field Tokens
-- ════════════════════════════════════════════════════════════

/-- Token for diffusion correction in OrderedFieldTokens framework. -/
inductive DiffusionToken (shape : ImageShape)
  | applyDifferentialCorrection (t : Timestep) (lambda : Q1616)
  | estimateSNRBias (t : Timestep)
  | correctWithGuidance (strategy : GuidanceStrategy shape)
  deriving Repr

-- ════════════════════════════════════════════════════════════
-- §8  Verification Examples (AGENTS.md §4 requirement)
-- ════════════════════════════════════════════════════════════

#eval Q1616.ofNat 100  -- 100.0 in Q16.16
#eval Q1616.sqrt (Q1616.ofNat 4)  -- ~2.0 in Q16.16

#eval GuidanceStrategy.defaultLinear { height := 1, width := 1, channels := 1 } (Q1616.ofNat 1) 1000
-- Linear schedule from 1.0 down to 0.0 over 1000 steps

end Semantics.DiffusionSNRBias
