Zulip Chat Archive

Stream: lean4

Topic: Basic unverified symbolic calculations in Lean4 using PARI


Anand Rao Tadipatri (Apr 05 2023 at 15:45):

I have implemented some code for performing some quick unverified arithmetic and symbolic calculations in Lean4, using PARI/GP as a backend (this was inspired by a comment of @Siddhartha Gadgil after a lecture by @Kevin Buzzard today). This kind of interface is ideal for cases where one wants to do some quick calculations, say to plug in a suitable expression to close the goal, without leaving the Lean editor. The code is currently limited to only calculations involving the basic arithmetic operations, but can (in principle) be extended to do more.

Running it requires having PARI/GP installed; on Arch Linux, this can be done by running pacman -S pari. The code is available here, and is about 50 lines long.

import Lean

open Lean Elab Term Meta Tactic

declare_syntax_cat arith
syntax str : arith
syntax ident : arith
syntax num : arith
syntax arith "+" arith : arith
syntax arith "-" arith : arith
syntax arith "*" arith : arith
syntax arith "/" arith : arith
syntax arith "^" arith : arith
syntax "(" arith ")" : arith

partial def arithToString : TSyntax `arith  String
  | `(arith| $x:str) => x.getString
  | `(arith| $a:ident) => toString a.getId
  | `(arith| $n:num) => toString n.getNat
  | `(arith| $a + $b) => s!"{arithToString a} + {arithToString b}"
  | `(arith| $a - $b) => s!"{arithToString a} - {arithToString b}"
  | `(arith| $a * $b) => s!"{arithToString a} * {arithToString b}"
  | `(arith| $a / $b) => s!"{arithToString a} / {arithToString b}"
  | `(arith| $a ^ $b) => s!"{arithToString a} ^ {arithToString b}"
  | `(arith| ($a)) => s!"({arithToString a})"
  | _ => panic! "Invalid `arith` syntax"

open IO.Process in -- code by Max Nowak from https://leanprover.zulipchat.com/#narrow/stream/270676-lean4/topic/external.20process/near/345090183
/-- Pipe input into stdin of the spawned process, then return output upon completion. -/
def cmd_with_stdin (args : SpawnArgs) (input : String) : IO Output := do
  let child <- spawn { args with stdin := .piped, stdout := .piped, stderr := .piped }
  let (stdin, child) <- child.takeStdin
  stdin.putStr input
  stdin.flush
  let stdout <- IO.asTask child.stdout.readToEnd Task.Priority.dedicated
  let stderr <- child.stderr.readToEnd
  let exitCode <- child.wait
  let stdout <- IO.ofExcept stdout.get
  return { exitCode, stdout, stderr }

def queryPari (a : TSyntax `arith) : IO String := do
  let out  cmd_with_stdin
              { cmd := "gp", args := #["-q"]}
              <| arithToString a
  return out.stdout

elab "#symbolic" e:arith : command => do
  -- dbg_trace (arithToString e)
  let out  queryPari e
  logInfoAt e out

#symbolic 2 + 2                    -- 4
#symbolic (x ^ 2 - 1) / (x + 1)    -- x - 1
#symbolic (x + 2) * (8 * x + 17)   -- 8*x^2 + 33*x + 34

Kevin Buzzard (Apr 05 2023 at 16:22):

Ha ha! I was switching windows to do polynomial calculations and then verifying the solutions in lean. Nice!

Shreyas Srinivas (Apr 05 2023 at 17:53):

Can this be integrated with mathlib? Seems useful in many places.

Siddhartha Gadgil (Apr 06 2023 at 03:21):

By the way @Anand Rao it may be useful to have a term elaborator with this (using runParserCategorty).

Miguel Marco (Apr 06 2023 at 23:10):

Can this same technique be used with other computer algebra systems, or is it pari specific?

I was thinking, for example, of using it for running polyrith without depending on an internet connection.

Anand Rao Tadipatri (Apr 07 2023 at 12:53):

Shreyas Srinivas said:

Can this be integrated with mathlib? Seems useful in many places.

I'd be glad to open a PR to mathlib4, perhaps after adding a few minor improvements.

Anand Rao Tadipatri (Apr 07 2023 at 13:07):

Miguel Marco said:

Can this same technique be used with other computer algebra systems, or is it pari specific?

I was thinking, for example, of using it for running polyrith without depending on an internet connection.

In principle, it can be used with any computer algebra system that supports a command-line interface. It should be possible to use such a set-up for polyrith - where the tactic first attempts to run the computation with a locally-installed CAS and turns to using the internet only when the user has no CAS installed locally.

Anand Rao Tadipatri (Apr 07 2023 at 14:18):

Siddhartha Gadgil said:

By the way Anand Rao it may be useful to have a term elaborator with this (using runParserCategorty).

I have now added a tactic which interprets the answer back as a Lean term and attempts to prove that it is equal to the original expression using ring/linarith/norm_num.

def arithToTerm (a : TSyntax `arith) : TacticM <| TSyntax `term := do
  let .some s  pure a.raw.reprint | failure
  let .ok stx  pure $ Parser.runParserCategory ( getEnv) `term s | failure
  return stx

elab "symb_calc" a:arith : tactic => do
  let out  queryPari <| arithToString a
  let res  ofExcept <| Parser.runParserCategory ( getEnv) `arith out
  let lhs : TSyntax `term  arithToTerm a
  let rhs : TSyntax `term  arithToTerm res
  logInfo m!"{lhs} = {rhs}"
  evalTactic <|  `(tactic| have : $lhs = $rhs := ?_; swap; first | ring | linarith | norm_num)

example (a : Int) : (a + 3) * (a + 5) = (a + 5) * (a + 3) := by
  symb_calc (a + 3) * (a + 5) -- (a + 3) * (a + 5) = a ^ 2 + 8 * a + 15
  rw [this]
  symb_calc (a + 5) * (a + 3) -- (a + 5) * (a + 3) = a ^ 2 + 8 * a + 15
  rw [this]

Anand Rao Tadipatri (Apr 07 2023 at 14:24):

I have also added a utility for running arbitrary PARI calculations from Lean, treating both the input and output as strings. Here is the code:

def queryPari (s : String) : IO String := do
  let out  cmd_with_stdin { cmd := "gp", args := #["-q"]} s
  return out.stdout

elab "#pari" c:str : command => do
  let out  queryPari c.getString
  logInfo out

#pari "nextprime(35)" -- 37

It will require a considerable amount of work to interpret the strings back as Lean terms for general PARI calculations.


Last updated: Dec 20 2023 at 11:08 UTC