Skip to content

Instantly share code, notes, and snippets.

@alok
Created July 16, 2024 00:25
Show Gist options
  • Save alok/b96c5162497d4d3975fdbdcac453eb25 to your computer and use it in GitHub Desktop.
Save alok/b96c5162497d4d3975fdbdcac453eb25 to your computer and use it in GitHub Desktop.
import Mathlib
import Lean.Data.Parsec
import Batteries.Lean.HashMap
import Lean.Data.HashMap
import Lean.Util.Path
import Lean.Data.Rat
import LeanInf.Basic
-- set_option diagnostics true
theorem two_plus_two : 2 + 2 = 4 := rfl
-- TODO rm? too overlapping-/
instance [Neg a] [Add a] : Sub a where
sub x y := x + (-y)
@[inherit_doc Float]
abbrev Coeff := Float
@[inherit_doc Rat]
abbrev Exponent := Rat
/-- A term in a polynomial. Given as `(Coeff, Exponent)`. -/
abbrev Term := Coeff × Exponent
/-- A map from exponents to coefficients -/
abbrev CoeffMap := Lean.HashMap Exponent Coeff -- TODO use RbMap instead bc sorted?
/-- A polynomial, represented as a `HashMap` from exponents to coefficients -/
structure Polynomial' where
coeffs : CoeffMap := default
deriving Repr, Inhabited, BEq
namespace Polynomial'
/-- Add two polynomials by adding their coefficients together -/
instance : Add Polynomial' where
add p q := Id.run do
let mut result := p.coeffs
for (exp, coeff) in q.coeffs do
let existingCoeff := result.findD exp 0
result := result.insert exp (existingCoeff + coeff)
return ⟨result⟩
/-- Negates a polynomial by negating its coefficients -/
instance : Neg Polynomial' where neg p := ⟨p.coeffs.mapValues (-·)⟩
#eval Polynomial'.mk #{01, 12} + Polynomial'.mk #{-11, 12}
/-- The empty polynomial, that is, the constant `0`. -/
def empty : Polynomial' := ⟨.empty⟩
/-- TODO: bad idea? Any real number can be represented as a polynomial with a single term. By the way, this also uses that 0^0 is 1 (since the constant term is x^0) -/
instance : Coe Coeff Polynomial' where
coe c := {coeffs := #{0 ↦ c}}
/-- Create a `Polynomial'` from a natural number -/
instance : OfNat Polynomial' n where ofNat := match n with
| 0 => .empty
| 1 => ⟨#{01}⟩
| _ => ⟨#{0 ↦ n.toFloat}⟩
/-- For decimal and scientific numbers (e.g., `1.23`, `3.12e10`).
Examples:
- `OfScientific.ofScientific 123 true 2` represents `1.23`
- `OfScientific.ofScientific 121 false 100` represents `121e100`
-/
instance : OfScientific Polynomial' where
ofScientific mantissa exponentSign decimalExponent := ⟨#{0 ↦ .ofScientific mantissa exponentSign decimalExponent}⟩
#eval Polynomial'.empty == (0 : Polynomial')
#eval Polynomial'.empty
#eval (0 )
/-- Create a `Polynomial'` from a list of tuples -/
def ofList (l : List Term) : Polynomial' := Id.run do
let mut result := .empty
for (coeff, exp) in l do
let existingCoeff := result.findD exp 0
result := result.insert exp (existingCoeff + coeff)
return ⟨result⟩
namespace Format
private def digitToSuperscript (c : Char) : Char :=
match c with
| '0' => '⁰' | '1' => '¹' | '2' => '²' | '3' => '³' | '4' => '⁴'
| '5' => '⁵' | '6' => '⁶' | '7' => '⁷' | '8' => '⁸' | '9' => '⁹'
| _ => c
/-- Converts digits 0-9 to their superscript Unicode equivalents, leaving other characters unchanged. Handles negative signs properly. -/
def toSuperscript (s : String) : String :=
let rec go (acc : String) (chars : List Char) : String :=
match chars with
| [] => acc
| '-' :: rest => go (acc ++ "⁻") rest
| c :: rest => go (acc.push (digitToSuperscript c)) rest
go "" s.data
private def sortTerms (terms : Array (Exponent × Coeff)) : Array (Exponent × Coeff) :=
terms.qsort (fun (a, _) (b, _) =>
if a < 0 && b < 0 then a > b
else if a < 0 then true
else if b < 0 then false
else a < b)
instance : ToString Polynomial' where
toString p := Id.run do
let terms := sortTerms (p.coeffs.toArray)
|>.filter (fun (_, coeff) => coeff != 0) -- Filter out zero terms
|>.map fun (exp, coeff) =>
match exp with
| 0 => s!"{coeff}"
| 1 => s!"{coeff}ε"
| -1 => s!"{coeff}H"
| n => Id.run do
let unit := if n > 0 then "ε" else "H"
let exp := Format.toSuperscript (toString (if exp > 0 then exp else -exp))
s!"{coeff}{unit}{exp}"
let nonZeroTerms := terms.filter (· ≠ "0") -- Remove "0" terms
return if nonZeroTerms.isEmpty then "0" else nonZeroTerms.intersperse " + "
#eval Format.toSuperscript s!"{-1}"
end Format
namespace Notation
/-- Syntax category for infinitesimal or hyperreal units -/
declare_syntax_cat infUnit
/-- Syntax for representing ε (epsilon) or H as infinitesimal or hyperfinite units -/
syntax "ε" : infUnit
syntax "H" : infUnit
/-- Syntax category for polynomial items-/
declare_syntax_cat polyItem
/-- Syntax for representing ε or H as a standalone polynomial item
Examples: `ε`, `H` -/
syntax infUnit : polyItem
syntax "-" infUnit : polyItem
/-- Syntax for representing a term multiplied by ε or H as a standalone polynomial item
Examples:
- `2ε`, `3H`
- `xε`, `yH` where `x` and `y` are variables -/
syntax term infUnit : polyItem
/-- Syntax for representing ε or H raised to a power as a standalone polynomial item
Examples:
- `ε^2`, `H^3`
- `ε^n`, `H^m` where `n` and `m` are variables -/
syntax infUnit "^" term : polyItem
syntax "-" infUnit "^" term : polyItem
/-- Syntax for representing a standalone term as a polynomial item
Examples:
- `1`
- `x` where `x` is a variable
- `2.5` for a floating-point number -/
syntax term : polyItem
/-- Syntax for representing a term multiplied by ε or H raised to a power as a standalone polynomial item
Examples:
- `2ε^3`, `3H^2`
- `xε^n`, `yH^m` where `x`, `y`, `n`, and `m` are variables -/
syntax term infUnit "^" term : polyItem
/-- Syntax for a polynomial like `p[1, 2ε, 3ε^2]` -/
syntax "p[" polyItem,* "]" : term
/--Semantics for polynomial notation.-/
macro_rules
| `(p[]) => `(Polynomial'.empty)
| `(p[ε]) => `(Polynomial'.mk #{11})
| `(p[-ε]) => `(Polynomial'.mk #{1 ↦ -1})
| `(p[H]) => `(Polynomial'.mk #{-11})
| `(p[-H]) => `(Polynomial'.mk #{-1 ↦ -1})
| `(p[$coeff:term ε]) => `(Polynomial'.mk #{1 ↦ $coeff})
| `(p[$coeff:term H]) => `(Polynomial'.mk #{-1 ↦ $coeff})
| `(p[ε^$exp]) => `(p[1ε^$exp])
| `(p[-ε^$exp]) => `(p[-1ε^$exp])
| `(p[H^$exp]) => `(p[1H^$exp])
| `(p[-H^$exp]) => `(p[-1H^$exp])
| `(p[$coeff:term ε^$exp]) => `(Polynomial'.mk #{$exp ↦ $coeff})
| `(p[$coeff:term H^$exp]) => `(Polynomial'.mk #{-$exp ↦ $coeff})
| `(p[$x:polyItem, $xs,*]) => `(p[$x] + p[$xs,*])
| `(p[$coeff:term]) => `(Polynomial'.mk #{0 ↦ $coeff})
#eval toString <| p[]
#eval toString <| p[ε]
#eval toString <| p[-ε]
#eval toString <| p[H]
#eval toString <| p[-H]
#eval toString <| p[-3H^3]
#eval toString <| p[-ε^3]
#eval toString <| p[1ε]
#eval toString <| p[1ε, 3H]
#eval Id.run do
let x := 2
(p[x ε], p[4*x ε^2])
#eval p[1, 2 ε, 3ε^2]
end Notation
end Polynomial'
#eval toString <| p[ 2ε, 3ε^2, 0 ε] + p[ 2ε, 3ε^2,4H^2]
#eval p[0 ε].coeffs.toArray.filter (fun (_, coeff) => coeff != 0)
def Polynomial'.norm (p : Polynomial') : Polynomial' := ⟨p.coeffs.mapValues (fun coeff => if coeff == 0 then 0 else 1)⟩
instance : Mul Polynomial' where
mul p q := Id.run do
let mut result := Polynomial'.empty
for (expP, coeffP) in p.coeffs do
for (expQ, coeffQ) in q.coeffs do
let newExp := expP + expQ
let newCoeff := coeffP * coeffQ
let term := ⟨#{newExp ↦ newCoeff}⟩
result := result + term
return result
#eval toString (Polynomial'.mk #{21, -13, 01}) -- Should output
#eval toString (Polynomial'.mk #{11, 24, 25, 31, 41, 51, 61, 71, 81, 91, 01}) -- Should output: "x + x² + x³ + x⁴ + x⁵ + x⁶ + x⁷ + x⁸ + x⁹ + ¹"
-- TODO use polynomial repr instead of raw hashmaps
structure LeviCivitaNum where -- extends Field
/-- the standard part of the Levi-Civita number -/
std : Coeff := 0
/-- the infinitely big (more properly, unlimited) part of the number -/
infinite : Polynomial' := .empty
/-- the infinitesimal part of the number -/
infinitesimal : Polynomial' := .empty
deriving Inhabited,BEq
instance : Repr LeviCivitaNum where
reprPrec x _ := Id.run do
let parts := #[
if x.infinite != .empty then toString x.infinite else "",
if x.std != 0 then toString x.std else "",
if x.infinitesimal != .empty then toString x.infinitesimal else ""
]
let nonEmptyParts := parts.filter (fun a => !a.isEmpty && a != "0")
return if nonEmptyParts.isEmpty then "0" else nonEmptyParts.intersperse " + "
-- TODO this should be doable with default deriving handler for `Add`.
instance : Add LeviCivitaNum where
add x y := {std := x.std + y.std, infinite := x.infinite + y.infinite, infinitesimal := x.infinitesimal + y.infinitesimal}
instance : Coe Coeff LeviCivitaNum where
coe c := {std := c}
instance : OfNat LeviCivitaNum n where
ofNat := match n with
| 0 => {std := 0}
| 1 => {std := 1}
| _ => {std := n.toFloat}
instance : OfScientific LeviCivitaNum where
ofScientific mantissa exponentSign decimalExponent := {std := .ofScientific mantissa exponentSign decimalExponent}
def LeviCivitaNum.zero : LeviCivitaNum := 0
instance : Zero LeviCivitaNum where zero := 0
#eval (0 : LeviCivitaNum)
def LeviCivitaNum.one : LeviCivitaNum := 1
instance : One LeviCivitaNum where one := 1
def LeviCivitaNum.ε : LeviCivitaNum := {infinitesimal := ⟨#{11}⟩}
/-- `H` is a hyperfinite number, synonyms are "infinitely big number" and "unlimited number".-/
def LeviCivitaNum.H : LeviCivitaNum := {infinite := ⟨#{-11}⟩}
#eval (.ε : LeviCivitaNum)
instance : Coe Term LeviCivitaNum where
coe term := Id.run do
let (coeff, exp) := term
let mut out : LeviCivitaNum := default
return match cmp exp 0 with
| .eq => ↑coeff
| .gt => {out with infinite := ⟨#{exp ↦ coeff}⟩}
| .lt => {out with infinitesimal := ⟨#{exp ↦ coeff}⟩}
/-- Create a `LeviCivitaNum` from a list of tuples representing the coefficients and exponents of the polynomial -/
def LeviCivitaNum.ofList (l : List Term) : LeviCivitaNum := l.foldr (fun t acc => acc + t) 0
/-- Create a `LeviCivitaNum` from an array of tuples representing the coefficients and exponents of the polynomial -/
def LeviCivitaNum.ofArray (l : Array (Coeff × Exponent)) : LeviCivitaNum := l.foldr (fun t acc => acc + (↑t)) 0
-- TODO debug why this was broken (poly addition)
-- infinitesimal := infinitesimal + Polynomial'.mk #{exp ↦ existingCoeff + coeff}
#eval LeviCivitaNum.ofList [(1, 1)]
-- Similar syntax for LeviCivita numbers
syntax "lc[" polyItem,* "]" : term
/--Semantics for creating LeviCivitaNum from a list of terms like `lc[ε, H, ε^2, H^2]`, which equals `ε + H + ε^2 + H^2`-/
macro_rules
| `(lc[]) => `(LeviCivitaNum.zero)
| `(lc[ε]) => `(LeviCivitaNum.ε)
| `(lc[-ε]) => `({ infinitesimal := ⟨#{1 ↦ -1}⟩ : LeviCivitaNum})
| `(lc[H]) => `(LeviCivitaNum.H)
| `(lc[-H]) => `({ infinite := ⟨#{-1 ↦ -1}⟩ : LeviCivitaNum})
| `(lc[$coeff:term ε]) => `({ infinitesimal := ⟨#{1 ↦ $coeff}⟩ : LeviCivitaNum})
| `(lc[$coeff:term H]) => `({ infinite := ⟨#{-1 ↦ $coeff}⟩ : LeviCivitaNum})
| `(lc[ε^$exp]) => `(lc[1ε^$exp])
| `(lc[-ε^$exp]) => `(lc[-1ε^$exp])
| `(lc[H^$exp]) => `(lc[1H^$exp])
| `(lc[-H^$exp]) => `(lc[-1H^$exp])
| `(lc[$coeff:term ε^$exp]) => `({ infinitesimal := ⟨#{$exp ↦ $coeff}⟩ : LeviCivitaNum})
| `(lc[$coeff:term H^$exp]) => `({ infinite := ⟨#{-$exp ↦ $coeff}⟩ : LeviCivitaNum})
| `(lc[$x:polyItem, $xs,*]) => `(lc[$x] + lc[$xs,*])
| `(lc[$coeff:term]) => `({ std := $coeff : LeviCivitaNum})
#eval lc[]
#eval lc[1ε]
#eval lc[3ε]
#eval lc[ε]
#eval lc[ε^2]
#eval lc[ε^2 , H^2]
#eval lc[1ε , 2H]
instance : Neg LeviCivitaNum where
neg x := {std := -x.std, infinite := -x.infinite, infinitesimal := -x.infinitesimal}
-- Example usage and test
#eval toString <| p[2ε, 3ε^2] * p[1ε, 3ε^2]
-- 2e + 3e^3 * 1ε + 4ε^3
local instance [BEq k][Hashable k] : Append (Lean.HashMap k v) where
append x y := Id.run do
let mut result := x
for (k, v) in y do
result := result.insert k v
return result
#eval #{ 11, 22} ++ #{12, 23}
private def Polynomial'.partition (poly: Polynomial') : LeviCivitaNum := Id.run do
let mut (std, infinite, infinitesimal) := (0, p[], p[])
for (exp, coeff) in poly.coeffs do
if exp == 0 then
std := std + coeff
else if exp < 0 then
infinite := infinite + p[coeff H^(-exp)]
else
infinitesimal := infinitesimal + p[coeff ε^exp]
return {std := std, infinite := infinite, infinitesimal := infinitesimal}
open Polynomial'.Format in
instance : ToString LeviCivitaNum where
toString x := Id.run do
let mut terms := #[]
-- Add infinite terms
let infiniteTerms := sortTerms (x.infinite.coeffs.toArray)
|>.map fun (exp, coeff) =>
if coeff == 0 then
""
else
match exp with
| -1 => s!"{coeff}H"
| 0 => s!"{coeff}"
| 1 => s!"{coeff}H⁻¹"
| _ =>
let unit := "H"
let exp := toSuperscript (toString (-exp))
s!"{coeff}{unit}{exp}"
terms := terms.append infiniteTerms
-- Add standard part if non-zero
if x.std != 0 then
terms := terms.push s!"{x.std}"
-- Add infinitesimal terms
let infinitesimalTerms := sortTerms (x.infinitesimal.coeffs.toArray)
|>.map fun (exp, coeff) =>
if coeff == 0 then
""
else
match exp with
| 0 => s!"{coeff}"
| 1 => s!"{coeff}ε"
| _ =>
let unit := "ε"
let exp := toSuperscript (toString exp)
s!"{coeff}{unit}{exp}"
terms := terms.append infinitesimalTerms
let nonEmptyParts := terms.filter (· != "")
-- Combine all terms
let result := nonEmptyParts.intersperse " + "
return if result.isEmpty then "0" else result
-- parenthesization is fucked
#eval toString <| p[1, 2ε, 4H, 4H, 3,3ε^2].partition
instance : Mul LeviCivitaNum where
mul x y :=
/- Merge all coefficients into a single Polynomial' and use underlying polynomial multiplication, then split again.
-/
let x' := p[x.std] + x.infinite + x.infinitesimal
let y' := p[y.std] + y.infinite + y.infinitesimal
-- Multiply the polynomials
let result := (x' * y')
result.partition
#eval lc[ε]*lc[H,ε,H^2]
#eval lc[-ε, ε, H] * lc[-ε, ε, H]
#eval lc[-ε^2,2ε^3] * lc[ε, 3H]
#eval lc[ε^2, 3ε^4] * lc[ε, 3H]
#eval 0 - p[5H]
#eval 2.0>3.4
#synth LE Coeff
/-- check if the number is a standard number-/
def LeviCivitaNum.isStd (x: LeviCivitaNum) : Bool := x.infinite.coeffs.isEmpty && x.std != 0
/-- compute the signum of a Levi-Civita number-/
def LeviCivitaNum.signum (x: LeviCivitaNum) : LeviCivitaNum :=
let allTerms := x.infinite.coeffs.toArray ++ #[(0, x.std)] ++ x.infinitesimal.coeffs.toArray
-- we sort by < since currently all exponent keys are negative for unlimited and positive for infinitesimals, which is a bad convention because opposite of scientific notation.
let sortedTerms := allTerms.qsort (fun (exp₁, _) (exp₂, _) => exp₁ < exp₂)
match sortedTerms.find? (fun (_, coeff) => coeff != 0) with
| some (_, coeff) => if _h:coeff > 0 then 1 else -1
| none => 0
#eval lc[1, H, -H^2].signum -- Should be -1
#eval lc[-ε,H,ε,H,-H^2, H^5].signum -- Should be -1
#eval lc[ε].signum -- Should be 1
#eval lc[-H,ε].signum -- Should be -1
#eval lc[-ε,H].signum -- Should be 1
#eval lc[-2,ε,H].signum -- Should be 1
instance : LT LeviCivitaNum where
lt x y := (x - y).signum == -1
#reduce (1:LeviCivitaNum) < (2:LeviCivitaNum)
#eval 1<2
#eval lc[1] < lc[2]
#eval lc[ε] < lc[2ε]
#eval lc[H] < lc[2H]
#eval lc[-H] < lc[H]
#reduce Decidable (1>2)
def Polynomial'.mapCoeffs (f : Coeff → Coeff) (p : Polynomial') : Polynomial' :=
⟨p.coeffs.mapValues f⟩
/-- Compute the absolute value of a `LeviCivitaNum`-/
def LeviCivitaNum.abs (x : LeviCivitaNum) : LeviCivitaNum := {std := x.std, infinite := x.infinite.mapCoeffs Float.abs , infinitesimal := x.infinitesimal.mapCoeffs Float.abs}
/--
Expand a Taylor series for a LeviCivita number. `t` is a list of coefficients for the Taylor series.
TODO what does this do?
-/
def LeviCivitaNum.expand (x : LeviCivitaNum) (t : List Coeff) : LeviCivitaNum := Id.run do
let mut s := LeviCivitaNum.zero
let mut pow := LeviCivitaNum.one
for coeff in t do
let term := (lc[coeff]) * pow
s := s + term
pow := pow * x
return s
-- Test the expand function
#eval LeviCivitaNum.expand lc[1ε] [1, 1, 1/2, 1/6] -- Should approximate e^x
-- instance : Inv LeviCivitaNum where
/-- d represents epsilon since it's easier to type.-/
def testCases : List (String × Option LeviCivitaNum) := [
("1+d", some lc[1ε]),
("1/d", some lc[-ε]),
("d+d", some lc[2ε]),
("d^2", some lc[ε^2]),
("sqrt d", some lc[ε^(1/2)]),
("2+2", some lc[4]),
("2d", none),
("1+d", some lc[1ε]),
("1/d", some lc[-ε]),
("d+d", some lc[2ε]),
("a=6*7;a+5", some lc[47]),
("zzz=1;zzz==1", some lc[1]),
("f x=x^2;f(f(2))", some lc[16]),
("sqrt(-1)", none), -- Complex numbers not supported
("((1+i)/(sqrt 2))^8", none), -- Complex numbers not supported
("(1+d)^pi", none), -- Result is not a simple LeviCivita number
("d^pi", none),
("[sqrt(d+d^2)]^2", some lc[ε^(1/2) , ε^2])
]
/--entry point.-/
def main : IO Unit := do
IO.println <| System.FilePath.mk ".././"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment