Rat.frac

-- Rationals over Int.nova's Int, with a STRUCTURALLY non-zero
-- denominator.
--
-- Why not `(d : Int) Γ— (d β‰’ 0)`: has no π•Œ-code (there is no
-- code for Ξ© or for p in π•Œ β€” NovaFoundation.txt's Ξ© section says
-- so outright), so a Ξ£ carrying a proof cannot be a code and Rat would
-- be a large `type`: no Rat, no generic (A : π•Œ) combinators, no use
-- as a code anywhere. Worse, it would be practically uninhabited β€”
-- writing Β½ needs a proof of an Int DISEQUALITY. (When this was
-- written nothing inverted el-quot-eq at all; Int/effective.nova now
-- gives IntR back from a class equation, so the disequality is cheap β€”
-- but the encoding below is still the one that keeps Rat a π•Œ-code.)
--
-- So the denominator ranges over NZ, whose elements ARE the non-zero
-- integers by construction: inj₁ n and injβ‚‚ n denote +(n+1) and
-- -(n+1). Everything stays small, elements are free, and nzToInt
-- embeds NZ back into Int.
-- ===== the non-zero integers =====

import Natural (+, *, plusZeroId, zeroPlusId, plusComm, plusAssoc, swapLeft, multZeroId, zeroMult, sucMult, multComm, multDistrib)
import Int (Int, intZero, intOne, intNeg)
import Int.add (+, intAddComm)
import Core.equality (trans, cong)

NZ : π•Œ
NZ = β„• ⊎ β„•

-- +(n+1)
nzPos : β„• β†’ NZ using (Rat.frac.NZ.unfold)
nzPos = Ξ»n. inj₁ n

-- -(n+1)
nzNeg : β„• β†’ NZ using (Rat.frac.NZ.unfold)
nzNeg = Ξ»n. injβ‚‚ n

nzOne : NZ using (Rat.frac.NZ.unfold)
nzOne = nzPos Z

nzToInt : NZ β†’ Int using (Int.Int.unfold, Rat.frac.NZ.unfold)
nzToInt = λd. ⊎-elim (n. class (S n, Z)) (n. class (Z, S n)) d

-- the magnitudes multiply: (m+1)(k+1) = m*k + m + k + 1, so the stored
-- (magnitude βˆ’ 1) of the product is m*k + m + k; the sign is the usual
-- product of signs. Closure of NZ under multiplication is therefore
-- definitional β€” there is no side condition to discharge.
nzMul : NZ β†’ NZ β†’ NZ using (Rat.frac.NZ.unfold)
nzMul =
  λx y. ⊎-elim
    m. ⊎-elim (k. nzPos (m * k + m + k)) (k. nzNeg (m * k + m + k)) y
    m. ⊎-elim (k. nzNeg (m * k + m + k)) (k. nzPos (m * k + m + k)) y
    x

nzMulOneL : {y : NZ} β†’ nzMul nzOne y ≑ y
  using (nzMul.eq,
    nzNeg.eq,
    nzOne.eq,
    nzPos.eq,
    plusZeroId.rw,
    Rat.frac.NZ.unfold,
    zeroMult,
    zeroMult.rw,
    zeroPlusId,
    zeroPlusId.rw)
nzMulOneL = Ξ»y. ⊎-elim (k. ⋆) (k. ⋆) y

nzMulOneR : {x : NZ} β†’ nzMul x nzOne ≑ x
  using (multZeroId.rw,
    nzMul.eq,
    nzNeg.eq,
    nzOne.eq,
    nzPos.eq,
    plusZeroId.rw,
    Rat.frac.NZ.unfold,
    zeroPlusId,
    zeroPlusId.rw)
nzMulOneR = Ξ»x. ⊎-elim (m. ⋆) (m. ⋆) x

-- ===== nat lemmas, in the orientation the engine can use =====
--
-- Rewriting is ORIENTED and size-decreasing, so Natural.nova's
-- distributivity and successor-multiplication laws β€” both stated in
-- the size-INCREASING direction β€” never fire as rewrite rules. Their
-- flips do; each is proved by ⋆, the original firing by
-- whole-equation match on the flipped goal.
distribBack : (n m k : β„•) β†’ n * m + n * k ≑ n * (m + k) using (multDistrib)
distribBack = Ξ»n m k. ⋆

sucMultBack : (n m : β„•) β†’ m + n * m ≑ S n * m using (sucMult)
sucMultBack = Ξ»n m. ⋆

oneMult : (m : β„•) β†’ S Z * m ≑ m using (multComm)
oneMult = Ξ»m. S Z * m β‰‘βŸ¨ sucMult Z m ⟩ m + Z * m β‰‘βŸ¨ zeroMult m ⟩ m + Z β‰‘βŸ¨ plusZeroId m ⟩ m

-- ===== scaling an integer by a non-zero integer =====
--
-- Rational addition needs num Β· den, i.e. multiplication in Int. A
-- general intMul owes a quotient well-definedness proof in BOTH
-- arguments (the (aβ‚βˆ’aβ‚‚)(bβ‚βˆ’bβ‚‚) cross-sum identity). But one factor is
-- always a DENOMINATOR β€” an NZ, i.e. a SIGN and a NAT β€” so it suffices
-- to scale by a nat and negate: n Β· (a, b) = (n*a, n*b), whose
-- well-definedness is one application of distributivity.
intAddZeroL : (z : Int) β†’ intZero + z ≑ z
  using (hyp.rw,
    Int.Int.eq,
    Int.Int.unfold,
    Int.intZero.eq,
    Int.add.+.eq,
    plusComm,
    plusComm.rw,
    zeroPlusId,
    zeroPlusId.rw)
intAddZeroL = Ξ»z. quot-elim (p. ⋆) z

intAddZeroR : (z : Int) β†’ z + intZero ≑ z
  using (hyp.rw,
    Int.Int.eq,
    Int.Int.unfold,
    Int.intZero.eq,
    Int.add.+.eq,
    plusComm,
    plusComm.rw,
    plusZeroId,
    plusZeroId.rw,
    zeroPlusId,
    zeroPlusId.rw)
intAddZeroR = Ξ»z. quot-elim (p. ⋆) z

intScaleN : β„• β†’ Int β†’ Int using (distribBack, distribBack.rw, hyp.rw, Int.Int.unfold)
intScaleN = Ξ»n z. quot-elim (p. class (n * p .π₁, n * p .Ο€β‚‚)) z

intScaleNZero : (n : β„•) β†’ intScaleN n intZero ≑ intZero
  using (Int.Int.unfold, Int.intZero.eq, Rat.frac.intScaleN.eq)
intScaleNZero = Ξ»n. ⋆

intScaleNOne : (z : Int) β†’ intScaleN (S Z) z ≑ z
  using (hyp.rw,
    intScaleN.eq,
    Int.Int.eq,
    Int.Int.unfold,
    oneMult,
    oneMult.rw,
    plusComm,
    plusComm.rw)
intScaleNOne = Ξ»z. quot-elim (p. ⋆) z

-- scaling by n+1 really is n+1 iterated additions β€” the definition by
-- representatives agrees with the recursive one
intScaleNSuc : (n : β„•) (z : Int) β†’ intScaleN (S n) z ≑ z + intScaleN n z
  using (hyp.rw,
    intScaleN.eq,
    Int.Int.eq,
    Int.Int.unfold,
    Int.add.+.eq,
    sucMultBack,
    sucMultBack.rw)
intScaleNSuc = Ξ»n z. quot-elim (p. ⋆) z

intScale : NZ β†’ Int β†’ Int using (Int.Int.unfold, Rat.frac.NZ.unfold)
intScale = λd z. ⊎-elim (n. intScaleN (S n) z) (n. intNeg (intScaleN (S n) z)) d

intScaleOne : {z : Int} β†’ intScale nzOne z ≑ z
  using (intScale.eq, intScaleNOne, Int.Int.unfold, nzOne.eq, nzPos.eq)
intScaleOne = Ξ»z. ⋆

intScaleZero : (d : NZ) β†’ intScale d intZero ≑ intZero
  using (Int.Int.unfold,
    Int.intNeg.eq,
    Int.intZero.eq,
    Rat.frac.NZ.unfold,
    Rat.frac.intScale.eq,
    Rat.frac.intScaleN.eq)
intScaleZero = Ξ»d. ⊎-elim (n. ⋆) (n. ⋆) d

-- Adding a scaled zero. Both arguments of intAdd are quot-elim
-- SCRUTINEE positions, and a rewrite may not land in one unless the
-- subterm is β‡’α΄Ί-inferable (NovaKernel.txt Β§6, the neutral-subterm
-- rule) β€” which a stuck ⊎-elim like `intScale d intZero` is not. So
-- the ⊎-elim on d is done here, where each branch closes at the ROOT
-- (a type-determined position) against intAddZeroL/R, and the other
-- summand is kept a VARIABLE so there is nothing to rewrite in it.
-- Callers compose with trans rather than ask the engine to rewrite
-- these in place.
intAddScaleZeroL : {d : NZ} {y : Int} β†’ intScale d intZero + y ≑ y
  using (intAddZeroL,
    Int.Int.eq,
    Int.Int.unfold,
    Int.intNeg.eq,
    Int.intZero.eq,
    Int.add.+.eq,
    Natural.*.eq,
    Natural.+.eq,
    Rat.frac.NZ.unfold,
    Rat.frac.intScale.eq,
    Rat.frac.intScaleN.eq)
intAddScaleZeroL = λd y. ⊎-elim (n. intAddZeroL y) (n. intAddZeroL y) d

intAddScaleZeroR : {d : NZ} {y : Int} β†’ y + intScale d intZero ≑ y
  using (intAddZeroR, Int.Int.unfold, Rat.frac.NZ.unfold, Rat.frac.intScaleZero)
intAddScaleZeroR = λd y. ⊎-elim (n. intAddZeroR y) (n. intAddZeroR y) d

-- scaling by d agrees with multiplying by d's image in Int
intScaleToInt : {d : NZ} β†’ intScale d (class (S Z, Z)) ≑ nzToInt d
  using (Int.Int.unfold,
    Int.intNeg.eq,
    Natural.*.eq,
    Natural.+.eq,
    Rat.frac.NZ.unfold,
    Rat.frac.intScale.eq,
    Rat.frac.intScaleN.eq,
    Rat.frac.nzToInt.eq)
intScaleToInt = Ξ»d. ⊎-elim (n. ⋆) (n. ⋆) d

-- ===== the rationals =====
Rat : π•Œ
Rat = Int Γ— NZ

mkRat : Int β†’ NZ β†’ Rat using (Rat.frac.Rat.unfold)
mkRat = Ξ»n d. n, d

num : Rat β†’ Int using (Int.Int.unfold, Rat.frac.Rat.unfold)
num = Ξ»q. q .π₁

den : Rat β†’ NZ using (Rat.frac.NZ.unfold, Rat.frac.Rat.unfold)
den = Ξ»q. q .Ο€β‚‚

denInt : Rat β†’ Int using (Int.Int.unfold, Rat.frac.Rat.unfold)
denInt = Ξ»q. nzToInt (q .Ο€β‚‚)

ratOfInt : Int β†’ Rat using (Rat.frac.Rat.unfold)
ratOfInt = Ξ»z. mkRat z nzOne

ratZero : Rat using (Rat.frac.Rat.unfold)
ratZero = ratOfInt intZero

ratOne : Rat using (Rat.frac.Rat.unfold)
ratOne = ratOfInt intOne

-- n₁/d₁ + nβ‚‚/dβ‚‚ = (n₁·dβ‚‚ + nβ‚‚Β·d₁) / (d₁·dβ‚‚). Rat is a plain Ξ£-code β€”
-- pair construction, nothing to discharge. (Once Rat is quotiented by
-- cross-multiplication, ratAdd will owe a descent proof; that is the
-- next step, not this one.)
ratAdd : Rat β†’ Rat β†’ Rat using (Rat.frac.Rat.unfold)
ratAdd = Ξ»p q. mkRat (intScale (den q) (num p) + intScale (den p) (num q)) (nzMul (den p) (den q))

-- Β½ + Β½ = 4/4, unreduced, as the raw representation demands
half : Rat using (Rat.frac.Rat.unfold)
half = mkRat intOne (nzPos (S Z))

ratAddTest : ratAdd half half ≑ mkRat (class (4, Z)) (nzPos 3)
  using (Int.Int.unfold,
    Int.intOne.eq,
    Int.add.+.eq,
    Natural.*.eq,
    Natural.+.eq,
    Rat.frac.Rat.unfold,
    Rat.frac.den.eq,
    Rat.frac.half.eq,
    Rat.frac.intScale.eq,
    Rat.frac.intScaleN.eq,
    Rat.frac.mkRat.eq,
    Rat.frac.num.eq,
    Rat.frac.nzMul.eq,
    Rat.frac.nzPos.eq,
    Rat.frac.ratAdd.eq)
ratAddTest = ⋆

-- β…“ + (βˆ’β…“) = 0/(βˆ’9): numerator cancels, denominator keeps its sign
third : Rat using (Rat.frac.Rat.unfold)
third = mkRat intOne (nzPos 2)

negThird : Rat using (Rat.frac.Rat.unfold)
negThird = mkRat intOne (nzNeg 2)

-- the numerator's cancellation, at the root where the quotient join is
-- available (under the test's pair it is a component position, which
-- the strict engine reaches only by whole-equation match)
threeCancel : class (3, 3) ≑ intZero using (Int.Int.unfold, Int.intZero.eq)
threeCancel = ⋆

ratAddTest2 : ratAdd third negThird ≑ mkRat intZero (nzNeg 8)
  using (Int.Int.unfold,
    Int.intNeg.eq,
    Int.intOne.eq,
    Int.add.+.eq,
    Natural.*.eq,
    Natural.+.eq,
    Rat.frac.Rat.unfold,
    Rat.frac.den.eq,
    Rat.frac.intScale.eq,
    Rat.frac.intScaleN.eq,
    Rat.frac.mkRat.eq,
    Rat.frac.negThird.eq,
    Rat.frac.num.eq,
    Rat.frac.nzMul.eq,
    Rat.frac.nzNeg.eq,
    Rat.frac.nzPos.eq,
    Rat.frac.ratAdd.eq,
    Rat.frac.third.eq,
    threeCancel)
ratAddTest2 = trans _ _ _ ⋆ (cong (Ξ»u. Rat) (Ξ»x. mkRat x (nzNeg 8)) threeCancel)

-- ===== 0/1 is a unit =====
-- componentwise first: the numerator collapses by the scaled-zero
-- unit followed by intScaleOne, the denominator by nzMulOne
ratAddZeroLNum : {q : Rat} β†’ intScale (den q) intZero + intScale nzOne (num q) ≑ num q
  using (intScaleNOne)
ratAddZeroLNum = Ξ»q. trans _ (intScale nzOne (num q)) _ intAddScaleZeroL intScaleOne

ratAddZeroRNum : {q : Rat} β†’ intScale nzOne (num q) + intScale (den q) intZero ≑ num q
  using (intScaleNOne)
ratAddZeroRNum = Ξ»q. trans _ (intScale nzOne (num q)) _ intAddScaleZeroR intScaleOne

-- Ξ£-Ξ·, as a step of its own: el-sigma-eta is judgemental in the
-- theory but the kernel's normal forms do not contract (t.π₁ , t.Ο€β‚‚),
-- so the last hop has to be named
ratEta : {q : Rat} β†’ mkRat (num q) (den q) ≑ q
  using (den.eq,
    Int.Int.unfold,
    mkRat.eq,
    num.eq,
    Rat.frac.NZ.unfold,
    Rat.frac.Rat.unfold,
    sigma.eta)
ratEta = Ξ»q. ⋆

-- and then the pair, one component at a time
ratAddZeroL : {q : Rat} β†’ ratAdd ratZero q ≑ q
  using (Int.Int.unfold,
    Int.intNeg.eq,
    Int.intZero.eq,
    Int.add.+.eq,
    nzMulOneL,
    ratEta,
    Rat.frac.NZ.unfold,
    Rat.frac.Rat.eq,
    Rat.frac.Rat.unfold,
    Rat.frac.den.eq,
    Rat.frac.intScale.eq,
    Rat.frac.intScaleN.eq,
    Rat.frac.mkRat.eq,
    Rat.frac.num.eq,
    Rat.frac.nzMul.eq,
    Rat.frac.nzNeg.eq,
    Rat.frac.nzOne.eq,
    Rat.frac.nzPos.eq,
    Rat.frac.ratAdd.eq,
    Rat.frac.ratOfInt.eq,
    Rat.frac.ratZero.eq)
ratAddZeroL =
  Ξ»q. trans
    _
    _
    _
    trans
      ratAdd ratZero q
      _
      q .π₁, q .Ο€β‚‚
      cong
        Ξ»w. Rat
        Ξ»x. mkRat x (nzMul nzOne (den q))
        {intScale (den q) intZero + intScale nzOne (num q)}
        {num q}
        ratAddZeroLNum
      cong (Ξ»w. Rat) (Ξ»d. mkRat (num q) d) {nzMul nzOne (den q)} {den q} nzMulOneL
    ratEta

ratAddZeroR : {q : Rat} β†’ ratAdd q ratZero ≑ q
  using (Int.Int.unfold,
    Int.intNeg.eq,
    Int.intZero.eq,
    Int.add.+.eq,
    nzMulOneR,
    ratEta,
    Rat.frac.NZ.unfold,
    Rat.frac.Rat.eq,
    Rat.frac.Rat.unfold,
    Rat.frac.den.eq,
    Rat.frac.intScale.eq,
    Rat.frac.intScaleN.eq,
    Rat.frac.mkRat.eq,
    Rat.frac.num.eq,
    Rat.frac.nzMul.eq,
    Rat.frac.nzNeg.eq,
    Rat.frac.nzOne.eq,
    Rat.frac.nzPos.eq,
    Rat.frac.ratAdd.eq,
    Rat.frac.ratOfInt.eq,
    Rat.frac.ratZero.eq)
ratAddZeroR =
  Ξ»q. trans
    _
    _
    _
    trans
      ratAdd q ratZero
      _
      q .π₁, q .Ο€β‚‚
      cong
        Ξ»w. Rat
        Ξ»x. mkRat x (nzMul (den q) nzOne)
        {intScale nzOne (num q) + intScale (den q) intZero}
        {num q}
        ratAddZeroRNum
      cong (Ξ»w. Rat) (Ξ»d. mkRat (num q) d) {nzMul (den q) nzOne} {den q} nzMulOneR
    ratEta

-- ===== commutativity =====
-- (x + m) + k ≑ (x + k) + m: the addends the cross-sum needs swapped
plusSwapRight : {x m k : β„•} β†’ x + m + k ≑ x + k + m
  using (hyp.rw, plusAssoc, plusAssoc.rw, plusComm, plusComm.rw, swapLeft, swapLeft.rw)
plusSwapRight = Ξ»x m k. ⋆

-- the magnitude arithmetic of nzMul is symmetric: multComm on the
-- product, then the swap above on the two summands
mulPlusComm : {m k : β„•} β†’ m * k + m + k ≑ k * m + k + m using (plusAssoc)
mulPlusComm = Ξ»m k. trans _ _ _ (cong (Ξ»u. β„•) (Ξ»w. w + m + k) (multComm k m)) plusSwapRight

nzMulComm : {x y : NZ} β†’ nzMul x y ≑ nzMul y x
  using (plusAssoc, Rat.frac.NZ.unfold, Rat.frac.nzMul.eq, Rat.frac.nzNeg.eq, Rat.frac.nzPos.eq)
nzMulComm =
  λx y. ⊎-elim
    m. ⊎-elim
      w. nzMul (nzPos m) w ≑ nzMul w (nzPos m)
      k. cong (Ξ»u. NZ) nzPos {m * k + m + k} {k * m + k + m} mulPlusComm
      k. cong (Ξ»u. NZ) nzNeg {m * k + m + k} {k * m + k + m} mulPlusComm
      y
    m. ⊎-elim
      w. nzMul (nzNeg m) w ≑ nzMul w (nzNeg m)
      k. cong (Ξ»u. NZ) nzNeg {m * k + m + k} {k * m + k + m} mulPlusComm
      k. cong (Ξ»u. NZ) nzPos {m * k + m + k} {k * m + k + m} mulPlusComm
      y
    x

-- p + q ≑ q + p: intAddComm on the numerator, nzMulComm on the
-- denominator (the middle term of the trans spelled out β€” the
-- elaborator has nothing else to pin it against)
ratAddComm : (p q : Rat) β†’ ratAdd p q ≑ ratAdd q p
  using (Int.Int.unfold,
    Int.add.+.eq,
    Rat.frac.NZ.unfold,
    Rat.frac.Rat.unfold,
    Rat.frac.den.eq,
    Rat.frac.intScale.eq,
    Rat.frac.mkRat.eq,
    Rat.frac.num.eq,
    Rat.frac.nzMul.eq,
    Rat.frac.ratAdd.eq)
ratAddComm =
  Ξ»p q. trans
    _
    mkRat (intScale (den p) (num q) + intScale (den q) (num p)) (nzMul (den p) (den q))
    _
    cong
      Ξ»u. Rat
      Ξ»x. mkRat x (nzMul (den p) (den q))
      intAddComm (intScale (den q) (num p)) (intScale (den p) (num q))
    cong
      Ξ»u. Rat
      Ξ»d. mkRat (intScale (den p) (num q) + intScale (den q) (num p)) d
      {nzMul (den p) (den q)}
      {nzMul (den q) (den p)}
      nzMulComm

-- ===== negation =====
-- negate the numerator, keep the denominator
ratNeg : Rat β†’ Rat using (Rat.frac.Rat.unfold)
ratNeg = Ξ»p. mkRat (intNeg (num p)) (den p)

ratNegHalf : ratNeg half ≑ mkRat (class (Z, S Z)) (nzPos (S Z))
  using (Int.Int.unfold,
    Int.intNeg.eq,
    Int.intOne.eq,
    Rat.frac.Rat.unfold,
    Rat.frac.den.eq,
    Rat.frac.half.eq,
    Rat.frac.mkRat.eq,
    Rat.frac.num.eq,
    Rat.frac.nzPos.eq,
    Rat.frac.ratNeg.eq)
ratNegHalf = ⋆