/-
## Interface for MLIR dialects
-/

/-
### Extended types and attributes

Dialects can define custom types and attributes that map to concrete Lean
datatypes satisfying the requirements of the type interface. These types and
attributes can be mixed together with other dialects' and used in proofs.

Because it is impossible to know in advance what set of dialects an operation
will be used with, most proofs should either (1) quantify on a dialect
interface and handle default cases, or (2) come with a lifting theorem that
performs this closure under context automatically.
-/

/-- The `DialectTypeIntf` typeclass defines the requirements for dialect-
    supplied custom types. These properties are available even on unknown
    custom types, which allows type-generic functions and proofs to be written.

    In the interface, `σ` is the type of "signatures" (parameters), while `ε`
    is the type of "extended values". For instance for `builtin.tensor`, `σ` is
    a datatype holding the dimensions and base type of the tensor, while `ε` is
    the type of tensor values (for any particular signature).

    Type families that quantify over `Type` or higher universes are not
    supported to avoid universe polymorphism in the core data structures; in
    this case, `ε` can be made to implement specific instances of the family
    instead. -/
class DialectTypeIntf (σ: Type) (ε: σ → Type): Type where

  /-- The type should be inhabited, so that SSA value accesses can return
      defaults instead of requiring a proof that every access is dominated by
      a definition (which is a very annoying statement to maintain). -/
  inhabited: forall (s: σ), ε s

  /-- The signature should have decidable equality, so we can compare types. -/
  typeEq: DecidableEq σ
  /-- The type should have decidable equality, so that rewriting tools can
      find matches involving concrete values (eg. in PDL).
      Note: This might be relaxed into a `BEq` in the future. -/
  eq: forall (s: σ), DecidableEq (ε s)

  /-- String representation of values in the type (eg. a multi-dimensional
      array for tensors).  -/
  str: (s: σ) → ε s → String
  /-- String representation of the type itself (eg. "tensor<4x4xf64>"). -/
  typeStr: σ → String

  -- TODO: DialectTypeIntf: Type signature, to match eg. "any builtin.vector"

def DialectTypeIntf.sigType {σ ε} (_: DialectTypeIntf σ ε): Type := σ
def DialectTypeIntf.extType {σ ε} (_: DialectTypeIntf σ ε) (s: σ): Type := ε s

-- Expressing typeclass instances this way helps resolution
instance {σ ε} [i: DialectTypeIntf σ ε] (s: σ): Inhabited (ε s) where
  default := i.inhabited s
instance {σ ε} [i: DialectTypeIntf σ ε] (s: σ): ToString (ε s) where
  toString := i.str s
instance _DEq1 {σ ε} [i: DialectTypeIntf σ ε]: DecidableEq σ :=
  i.typeEq
instance _DEq2 {σ ε} [i: DialectTypeIntf σ ε] (s: σ): DecidableEq (ε s) :=
  i.eq s

/-- The `DialectAttrIntf` typeclass defines the requirements for dialect-
   supplied custom attributes. -/
class DialectAttrIntf (α: Type) where

  /-- The attribute should have decidable equality so that rewriting can match
      against them (eg. in PDL). -/
  eq: DecidableEq α

  /-- String representation of attribute values. -/
  str: α → String

  -- TODO: More data on attributes

def DialectAttrIntf.type {α} (_: DialectAttrIntf α): Type := α

instance _DEq3 {α} [i: DialectAttrIntf α]: DecidableEq α := i.eq

/-
### Combinations of interfaces

As dialects can provide multiple sets of extended types and attributes (and
dialects may themselves be combined), the interface must allow for different
extensions to be combined.

The following instances allow extended types, attributes, and dialects to be
combined with `Sum`.
-/

-- Like Sum.rec, but not a recursor (hence supported for code generation)
@[reducible]
def Sum.cases {α β γ} (fα: α → γ) (fβ: β → γ): (α ⊕ β) → γ
  | .inl a => fα a
  | .inr b => fβ b

instance {σ₁ ε₁ σ₂ ε₂} [i₁: DialectTypeIntf σ₁ ε₁] [i₂: DialectTypeIntf σ₂ ε₂]:
    DialectTypeIntf (σ₁ ⊕ σ₂) (Sum.cases ε₁ ε₂) where
  inhabited s :=
    match s with
    | .inl s₁ => i₁.inhabited s₁
    | .inr s₂ => i₂.inhabited s₂
  typeEq := inferInstance
  eq s :=
    match s with
    | .inl s₁ => i₁.eq s₁
    | .inr s₂ => i₂.eq s₂
  str s :=
    match s with
    | .inl s₁ => i₁.str s₁
    | .inr s₂ => i₂.str s₂
  typeStr := Sum.cases i₁.typeStr i₂.typeStr

instance {α₁ α₂} [i₁: DialectAttrIntf α₁] [i₂: DialectAttrIntf α₂]:
    DialectAttrIntf (α₁ ⊕ α₂) where
  eq := inferInstance
  str := Sum.cases i₁.str i₂.str

/-
### Dialects
-/

-- TODO: Document and finish the Dialect interface
class Dialect (α σ) (ε: σ → Type): Type :=
  name: String
  iα: DialectAttrIntf α
  iε: DialectTypeIntf σ ε

instance {α ε} [δ: Dialect α σ ε]: DialectAttrIntf α := δ.iα
instance {α ε} [δ: Dialect α σ ε]: DialectTypeIntf σ ε := δ.iε

/-
### Empty dialect

The empty dialect is a default value to start building hierarchies from. It is
used in a couple of aliases, eg. `MLIRTy` (for `MLIRType Dialect.empty`) and
`AttrVal` (for `AttrValue Dialect.empty`).
-/

inductive Void :=
deriving DecidableEq

instance: DialectTypeIntf Void (fun _ => Unit) where
  inhabited s := nomatch s
  typeEq      := inferInstance
  eq s        := nomatch s
  str s       := nomatch s
  typeStr s   := nomatch s

instance: DialectAttrIntf Void where
  eq          := inferInstance
  str a       := nomatch a

instance Dialect.empty: Dialect Void Void (fun _ => Unit) where
  name := "Empty"
  iα := inferInstance
  iε := inferInstance

-- We write combinations of dialects with + as usual (no risk of confusion)
instance {α₁ σ₁ ε₁ α₂ σ₂ ε₂}:
  HAdd (Dialect α₁ σ₁ ε₁) (Dialect α₂ σ₂ ε₂)
       (Dialect (α₁ ⊕ α₂) (σ₁ ⊕ σ₂) (Sum.cases ε₁ ε₂)) where
  hAdd δ₁ δ₂ := {
    name := s!"({δ₁.name}+{δ₂.name})"
    iα := inferInstance
    iε := inferInstance
  }

instance {α₁ σ₁ ε₁ α₂ σ₂ ε₂} [δ₁: Dialect α₁ σ₁ ε₁] [δ₂: Dialect α₂ σ₂ ε₂]:
    Dialect (α₁ ⊕ α₂) (σ₁ ⊕ σ₂) (Sum.cases ε₁ ε₂) :=
  δ₁ + δ₂

/-
### Coercions of dialects

The `CoeDialect` ckass is used to automatically inject individual dialects into
sums of dialects, which in turn allows automatic conversion of instances of
common MLIR data such as `MLIRType`, `AttrValue` and `Op` across dialects.
-/

class CoeDialect (δ₁: Dialect α₁ σ₁ ε₁) (δ₂: Dialect α₂ σ₂ ε₂) where
  coe_α: α₁ → α₂
  coe_σ: σ₁ → σ₂
  coe_ε: forall s, ε₁ s → ε₂ (coe_σ s)

instance (δ₁: Dialect α₁ σ₁ ε₁) (δ₂: Dialect α₂ σ₂ ε₂) [c: CoeDialect δ₁ δ₂]:
  Coe α₁ α₂ where coe := c.coe_α
instance (δ₁: Dialect α₁ σ₁ ε₁) (δ₂: Dialect α₂ σ₂ ε₂) [c: CoeDialect δ₁ δ₂]:
  Coe σ₁ σ₂ where coe := c.coe_σ
instance (δ₁: Dialect α₁ σ₁ ε₁) (δ₂: Dialect α₂ σ₂ ε₂) [c: CoeDialect δ₁ δ₂] s:
  Coe (ε₁ s) (ε₂ /-coe-/s) where coe := c.coe_ε s

instance (δ: Dialect α σ ε): CoeDialect δ δ where
  coe_α := id
  coe_σ := id
  coe_ε s := id

instance (δ₁: Dialect α₁ σ₁ ε₁) (δ₂: Dialect α₂ σ₂ ε₂):
    CoeDialect δ₁ (δ₁ + δ₂) where
  coe_α := .inl
  coe_σ := .inl
  coe_ε s := id

instance (δ₁: Dialect α₁ σ₁ ε₁) (δ₂: Dialect α₂ σ₂ ε₂):
    CoeDialect δ₂ (δ₁ + δ₂) where
  coe_α := .inr
  coe_σ := .inr
  coe_ε s := id

instance (δ: Dialect α σ ε): CoeDialect Dialect.empty δ where
  coe_α a := nomatch a
  coe_σ s := nomatch s
  coe_ε s := nomatch s


/-
Copyright (c) 2018 Simon Hudon. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Simon Hudon
-/
import data.dlist

/-- Concatenates a list of difference lists to form a single
difference list.  Similar to `list.join`. -/
def dlist.join {α : Type*} : list (dlist α) → dlist α
 | [] := dlist.empty
 | (x :: xs) := x ++ dlist.join xs

@[simp] lemma dlist_singleton {α : Type*} {a : α} :
  dlist.singleton a = dlist.lazy_of_list ([a]) := rfl

@[simp] lemma dlist_lazy {α : Type*} {l : list α} :
  dlist.lazy_of_list l = dlist.of_list l := rfl


/-
Copyright (c) 2019 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura, Mario Carneiro
-/
import Std.Data.List.Basic

namespace Std

/--
`AssocList α β` is "the same as" `List (α × β)`, but flattening the structure
leads to one fewer pointer indirection (in the current code generator).
It is mainly intended as a component of `HashMap`, but it can also be used as a plain
key-value map.
-/
inductive AssocList (α : Type u) (β : Type v) where
  /-- An empty list -/
  | nil
  /-- Add a `key, value` pair to the list -/
  | cons (key : α) (value : β) (tail : AssocList α β)
  deriving Inhabited

namespace AssocList

/--
`O(n)`. Convert an `AssocList α β` into the equivalent `List (α × β)`.
This is used to give specifications for all the `AssocList` functions
in terms of corresponding list functions.
-/
@[simp] def toList : AssocList α β → List (α × β)
  | nil => []
  | cons a b es => (a, b) :: es.toList

instance : EmptyCollection (AssocList α β) := ⟨nil⟩

@[simp] theorem empty_eq : (∅ : AssocList α β) = nil := rfl

/-- `O(1)`. Is the list empty? -/
def isEmpty : AssocList α β → Bool
  | nil => true
  | _   => false

@[simp] theorem isEmpty_eq (l : AssocList α β) : isEmpty l = l.toList.isEmpty := by
  cases l <;> simp [*, isEmpty, List.isEmpty]

/-- `O(n)`. Fold a monadic function over the list, from head to tail. -/
@[specialize] def foldlM [Monad m] (f : δ → α → β → m δ) : (init : δ) → AssocList α β → m δ
  | d, nil         => pure d
  | d, cons a b es => do foldlM f (← f d a b) es

@[simp] theorem foldlM_eq [Monad m] (f : δ → α → β → m δ) (init l) :
    foldlM f init l = l.toList.foldlM (fun d (a, b) => f d a b) init := by
  induction l generalizing init <;> simp [*, foldlM]

/-- `O(n)`. Fold a function over the list, from head to tail. -/
@[inline] def foldl (f : δ → α → β → δ) (init : δ) (as : AssocList α β) : δ :=
  Id.run (foldlM f init as)

@[simp] theorem foldl_eq (f : δ → α → β → δ) (init l) :
    foldl f init l = l.toList.foldl (fun d (a, b) => f d a b) init := by
  simp [List.foldl_eq_foldlM, foldl, Id.run]

/-- Optimized version of `toList`. -/
def toListTR (as : AssocList α β) : List (α × β) :=
  as.foldl (init := #[]) (fun r a b => r.push (a, b)) |>.toList

@[csimp] theorem toList_eq_toListTR : @toList = @toListTR := by
  funext α β as; simp [toListTR]
  exact .symm <| (Array.foldl_data_eq_map (toList as) _ id).trans (List.map_id _)

/-- `O(n)`. Run monadic function `f` on all elements in the list, from head to tail. -/
@[specialize] def forM [Monad m] (f : α → β → m PUnit) : AssocList α β → m PUnit
  | nil         => pure ⟨⟩
  | cons a b es => do f a b; forM f es

@[simp] theorem forM_eq [Monad m] (f : α → β → m PUnit) (l) :
    forM f l = l.toList.forM (fun (a, b) => f a b) := by
  induction l <;> simp [*, forM]

/-- `O(n)`. Map a function `f` over the keys of the list. -/
@[simp] def mapKey (f : α → δ) : AssocList α β → AssocList δ β
  | nil        => nil
  | cons k v t => cons (f k) v (mapKey f t)

@[simp] theorem mapKey_toList (f : α → δ) (l : AssocList α β) :
    (mapKey f l).toList = l.toList.map (fun (a, b) => (f a, b)) := by
  induction l <;> simp [*]

/-- `O(n)`. Map a function `f` over the values of the list. -/
@[simp] def mapVal (f : α → β → δ) : AssocList α β → AssocList α δ
  | nil        => nil
  | cons k v t => cons k (f k v) (mapVal f t)

@[simp] theorem mapVal_toList (f : α → β → δ) (l : AssocList α β) :
    (mapVal f l).toList = l.toList.map (fun (a, b) => (a, f a b)) := by
  induction l <;> simp [*]

/-- `O(n)`. Returns the first entry in the list whose entry satisfies `p`. -/
@[specialize] def findEntryP? (p : α → β → Bool) : AssocList α β → Option (α × β)
  | nil         => none
  | cons k v es => bif p k v then some (k, v) else findEntryP? p es

@[simp] theorem findEntryP?_eq (p : α → β → Bool) (l : AssocList α β) :
    findEntryP? p l = l.toList.find? fun (a, b) => p a b := by
  induction l <;> simp [findEntryP?]; split <;> simp [*]

/-- `O(n)`. Returns the first entry in the list whose key is equal to `a`. -/
@[inline] def findEntry? [BEq α] (a : α) (l : AssocList α β) : Option (α × β) :=
  findEntryP? (fun k _ => k == a) l

@[simp] theorem findEntry?_eq [BEq α] (a : α) (l : AssocList α β) :
    findEntry? a l = l.toList.find? (·.1 == a) := findEntryP?_eq ..

/-- `O(n)`. Returns the first value in the list whose key is equal to `a`. -/
def find? [BEq α] (a : α) : AssocList α β → Option β
  | nil         => none
  | cons k v es => match k == a with
    | true  => some v
    | false => find? a es

theorem find?_eq_findEntry? [BEq α] (a : α) (l : AssocList α β) :
    find? a l = (l.findEntry? a).map (·.2) := by
  induction l <;> simp [find?]; split <;> simp [*]

@[simp] theorem find?_eq [BEq α] (a : α) (l : AssocList α β) :
    find? a l = (l.toList.find? (·.1 == a)).map (·.2) := by simp [find?_eq_findEntry?]

/-- `O(n)`. Returns true if any entry in the list satisfies `p`. -/
@[specialize] def any (p : α → β → Bool) : AssocList α β → Bool
  | nil         => false
  | cons k v es => p k v || any p es

@[simp] theorem any_eq (p : α → β → Bool) (l : AssocList α β) :
    any p l = l.toList.any fun (a, b) => p a b := by induction l <;> simp [any, *]

/-- `O(n)`. Returns true if every entry in the list satisfies `p`. -/
@[specialize] def all (p : α → β → Bool) : AssocList α β → Bool
  | nil         => true
  | cons k v es => p k v && all p es

@[simp] theorem all_eq (p : α → β → Bool) (l : AssocList α β) :
    all p l = l.toList.all fun (a, b) => p a b := by induction l <;> simp [all, *]

/-- Returns true if every entry in the list satisfies `p`. -/
def All (p : α → β → Prop) (l : AssocList α β) : Prop := ∀ a ∈ l.toList, p a.1 a.2

/-- `O(n)`. Returns true if there is an element in the list whose key is equal to `a`. -/
@[inline] def contains [BEq α] (a : α) (l : AssocList α β) : Bool := any (fun k _ => k == a) l

@[simp] theorem contains_eq [BEq α] (a : α) (l : AssocList α β) :
    contains a l = l.toList.any (·.1 == a) := by
  induction l <;> simp [*, contains]

/--
`O(n)`. Replace the first entry in the list
with key equal to `a` to have key `a` and value `b`.
-/
@[simp] def replace [BEq α] (a : α) (b : β) : AssocList α β → AssocList α β
  | nil         => nil
  | cons k v es => match k == a with
    | true  => cons a b es
    | false => cons k v (replace a b es)

@[simp] theorem replace_toList [BEq α] (a : α) (b : β) (l : AssocList α β) :
    (replace a b l).toList =
    l.toList.replaceF (bif ·.1 == a then some (a, b) else none) := by
  induction l <;> simp [replace]; split <;> simp [*]

/-- `O(n)`. Remove the first entry in the list with key equal to `a`. -/
@[specialize, simp] def eraseP (p : α → β → Bool) : AssocList α β → AssocList α β
  | nil         => nil
  | cons k v es => bif p k v then es else cons k v (eraseP p es)

@[simp] theorem eraseP_toList (p) (l : AssocList α β) :
    (eraseP p l).toList = l.toList.eraseP fun (a, b) => p a b := by
  induction l <;> simp [List.eraseP, cond]; split <;> simp [*]

/-- `O(n)`. Remove the first entry in the list with key equal to `a`. -/
@[inline] def erase [BEq α] (a : α) (l : AssocList α β) : AssocList α β :=
  eraseP (fun k _ => k == a) l

@[simp] theorem erase_toList [BEq α] (a : α) (l : AssocList α β) :
    (erase a l).toList = l.toList.eraseP (·.1 == a) := eraseP_toList ..

/-- The implementation of `ForIn`, which enables `for (k, v) in aList do ...` notation. -/
@[specialize] protected def forIn [Monad m]
    (as : AssocList α β) (init : δ) (f : (α × β) → δ → m (ForInStep δ)) : m δ :=
  match as with
  | nil => pure init
  | cons k v es => do
    match (← f (k, v) init) with
    | ForInStep.done d  => pure d
    | ForInStep.yield d => es.forIn d f

instance : ForIn m (AssocList α β) (α × β) where
  forIn := AssocList.forIn

@[simp] theorem forIn_eq [Monad m] (l : AssocList α β) (init : δ)
    (f : (α × β) → δ → m (ForInStep δ)) : forIn l init f = forIn l.toList init f := by
  simp [forIn, List.forIn]
  induction l generalizing init <;> simp [AssocList.forIn, List.forIn.loop]
  congr; funext a; split <;> simp [*]

/-- Split the list into head and tail, if possible. -/
def pop? : AssocList α β → Option ((α × β) × AssocList α β)
  | nil => none
  | cons a b l => some ((a, b), l)

instance : ToStream (AssocList α β) (AssocList α β) := ⟨fun x => x⟩
instance : Stream (AssocList α β) (α × β) := ⟨pop?⟩

/-- Converts a list into an `AssocList`. This is the inverse function to `AssocList.toList`. -/
@[simp] def _root_.List.toAssocList : List (α × β) → AssocList α β
  | []          => nil
  | (a,b) :: es => cons a b (toAssocList es)

@[simp] theorem _root_.List.toAssocList_toList (l : List (α × β)) : l.toAssocList.toList = l := by
  induction l <;> simp [*]

@[simp] theorem toList_toAssocList (l : AssocList α β) : l.toList.toAssocList = l := by
  induction l <;> simp [*]


/-
Copyright (c) 2017 Daniel Selsam. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Author: Daniel Selsam

Labels.

Note: this file is just because strings are slow and cumbersome in current Lean.
-/
import .tactics
namespace certigrad

inductive label : Type
| default
| batch_start
| W_encode, W_encode₁, W_encode₂, h_encode, W_encode_μ, W_encode_logσ₂
| W_decode, W_decode₁, W_decode₂, h_decode, W_decode_p
| μ, σ, σ₂, log_σ₂, z, encoding_loss, decoding_loss, ε, x, p, x_all

namespace label

instance : decidable_eq label := by tactic.mk_dec_eq_instance

def to_str : label → string
| default := "<default>"
| batch_start := "batch_start"
| W_encode := "W_encode"
| W_encode₁ := "W_encode_1"
| W_encode₂ := "W_encode_2"
| h_encode := "h_encode"
| W_encode_μ := "W_encode_mu"
| W_encode_logσ₂ := "W_encode_logs₂"
| W_decode := "W_decode"
| W_decode₁ := "W_decode_1"
| W_decode₂ := "W_decode_2"
| h_decode := "h_decode"
| W_decode_p := "W_decode_p"
| μ := "mu"
| σ := "sigma"
| σ₂ := "sigma_sq"
| log_σ₂ := "log_s₂"
| z := "z"
| encoding_loss := "encoding_loss"
| decoding_loss := "decoding_loss"
| ε := "eps"
| x := "x"
| p := "p"
| x_all := "x_all"

instance : has_to_string label := ⟨to_str⟩

def to_nat : label → ℕ
| default := 0
| batch_start := 1
| W_encode := 2
| W_encode₁ := 3
| W_encode₂ := 4
| h_encode := 5
| W_encode_μ := 6
| W_encode_logσ₂ := 7
| W_decode := 8
| W_decode₁ := 9
| W_decode₂ := 10
| h_decode := 11
| W_decode_p := 12
| μ := 13
| σ := 14
| σ₂ := 15
| log_σ₂ := 16
| z := 17
| encoding_loss := 18
| decoding_loss := 19
| ε := 20
| x := 21
| p := 22
| x_all := 23

section proofs
open tactic

meta def prove_neq_case_core : tactic unit :=
do H ← intro `H,
   dunfold_at [`certigrad.label.to_nat] H,
   H ← get_local `H,
   (lhs, rhs) ← infer_type H >>= match_eq,
   nty ← mk_app `ne [lhs, rhs],
   assert `H_not nty,
   solve1 prove_nats_neq,
   exfalso,
   get_local `H_not >>= λ H_not, exact (expr.app H_not H)

lemma eq_of_to_nat_eq {x y : label} : x = y → to_nat x = to_nat y :=
begin
intro H,
subst H,
end

lemma to_nat_eq_of_eq {x y : label} : to_nat x = to_nat y → x = y :=
begin
cases x,
all_goals { cases y },
any_goals { intros, reflexivity },
all_goals { prove_neq_case_core }
end

lemma neq_of_to_nat {x y : label} : (x ≠ y) = (x^.to_nat ≠ y^.to_nat) :=
begin
apply propext,
split,
intros H_ne H_eq,
exact H_ne (to_nat_eq_of_eq H_eq),
intros H_ne H_eq,
exact H_ne (eq_of_to_nat_eq H_eq)
end

end proofs

def less_than (x y : label) : Prop := x^.to_nat < y^.to_nat

instance : has_lt label := ⟨less_than⟩

instance decidable_less_than (x y : label) : decidable (x < y) := by apply nat.decidable_lt

end label
end certigrad


/-
Copyright (c) 2022 Dhruv Bhatia. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Author(s): Dhruv Bhatia, Robert Y. Lewis, Mario Carneiro
-/
import Mathlib.Tactic.Polyrith

/-!

Each call to `polyrith` makes a call to the SageCell web API at
<https://sagecell.sagemath.org/>. To avoid making many API calls from CI,
we only test this communication in a few tests.

A full test suite is provided at the bottom of the file.

-/

/-!
## Set up testing infrastructre
-/

-- section tactic
-- open polyrith tactic
-- /--
-- For testing purposes, this behaves like `tactic.polyrith`, but takes an extra argument
-- representing the expected output from a call to Sage.
-- Allows for testing without actually making API calls.
-- -/
-- meta def tactic.test_polyrith (only_on : bool) (hyps : list pexpr)
--   (sage_out : json) (expected_args : list string) (expected_out : string) :
--   tactic unit := do
--   (eq_names, m, R, args) ← create_args only_on hyps,
--   guard (args = expected_args) <|>
--     fail!"expected arguments to Sage: {expected_args}\nbut produced: {args}",
--   out ← to_string <$> process_output eq_names m R sage_out,
--   guard (out = expected_out) <|>
--     fail!"expected final output: {expected_out}\nbut produced: {out}"

-- meta def format_string_list (input : list string) : format :=
-- "[" ++ (format.join $ (input.map (λ s, ("\"" : format) ++ format.of_string s ++ "\"")).intersperse ("," ++ format.line)) ++ "]"

-- setup_tactic_parser

-- meta def tactic.interactive.test_polyrith (restr : parse (tk "only")?)
--   (hyps : parse pexpr_list?)
--   (sage_out : string) (expected_args : list string) (expected_out : string) : tactic unit := do
--   some sage_out ← return $ json.parse sage_out,
--   tactic.test_polyrith restr.is_some (hyps.get_or_else []) sage_out expected_args expected_out

-- meta def tactic.interactive.test_sage_output (restr : parse (tk "only")?)
--   (hyps : parse pexpr_list?) (expected_out : string) : tactic unit := do
--   expected_json ← json.parse expected_out,
--   sleep 10, -- otherwise can lead to weird errors when actively editing code with polyrith calls
--   (eq_names, m, R, args) ← create_args restr.is_some (hyps.get_or_else []),
--   sage_out ← sage_output args,
--   guard (sage_out = expected_json) <|>
--     fail!"Expected output from Sage: {expected_out}\nbut produced: {sage_out}"

-- /--
-- A convenience function. Given a working test, prints the code for a call to `test_sage_output`.
-- -/
-- meta def tactic.interactive.create_sage_output_test (restr : parse (tk "only")?)
--   (hyps : parse pexpr_list?) : tactic unit := do
--   let hyps := (hyps.get_or_else []),
--   sleep 10, -- otherwise can lead to weird errors when actively editing code with polyrith calls
--   (eq_names, m, R, args) ← create_args restr.is_some hyps,
--   sage_out ← to_string <$> sage_output args,
--   let sage_out := sage_out.fold "" (λ s c, s ++ (if c = '"' then "\\\"" else to_string c)),
--   let onl := if restr.is_some then "only " else "",
--   let hyps := if hyps = [] then "" else to_string hyps,
--   trace!"test_sage_output {onl}{hyps} \"{sage_out}\""

-- /--
-- A convenience function. Given a working test, prints the code for a call to `test_polyrith`.
-- -/
-- meta def tactic.interactive.create_polyrith_test (restr : parse (tk "only")?)
--   (hyps : parse pexpr_list?) : tactic unit := do
--   let hyps := (hyps.get_or_else []),
--   sleep 10, -- otherwise can lead to weird errors when actively editing code with polyrith calls
--   (eq_names, m, R, args) ← create_args restr.is_some hyps,
--   sage_out ← sage_output args,
--   out ← to_string <$> process_output eq_names m R sage_out,
--   let out := out.fold "" (λ s c, s ++ (if c = '"' then "\\\"" else to_string c)),
--   let sage_out := (to_string sage_out).fold ""
--     (λ s c, s ++ (if c = '"' then "\\\"" else to_string c)),
--   let argstring := format_string_list args,
--   let onl := if restr.is_some then "only " else "",
--   let hyps := if hyps = [] then "" else to_string hyps,
--   let trf := format.nest 2 $ format!"test_polyrith {onl}{hyps} \n\"{sage_out}\"\n{argstring}\n\"{out}\"",
--   trace!"Try this: {trf}"

-- end tactic

-- /-!
-- ## SageCell communcation tests
-- -/

-- example (x y : ℚ) (h1 : x*y + 2*x = 1) (h2 : x = y) :
--   x*y = -2*y + 1 :=
-- begin
--   test_sage_output "{\"data\":[\"(poly.const 1/1)\",\"(poly.const -2/1)\"],\"success\":true}",
--   linear_combination h1 - 2 * h2
-- end

-- example (w x y z : ℝ) (h1 : x + 2.1*y + 2*z = 2) (h2 : x + 8*z + 5*w = -6.5)
--     (h3 : x + y + 5*z + 5*w = 3) :
--   x + 2.2*y + 2*z - 5*w = -8.5 :=
-- begin
--   test_sage_output "{\"data\":[\"(poly.const 2/1)\",\"(poly.const 1/1)\",\"(poly.const -2/1)\"],\"success\":true}",
--   linear_combination 2 * h1 + h2 - 2 * h3
-- end

-- /-! ### Standard Cases over ℤ, ℚ, and ℝ -/

-- example (x y : ℤ) (h1 : 3*x + 2*y = 10):
--   3*x + 2*y = 10 :=
-- by test_polyrith
--   "{\"data\":[\"(poly.const 1/1)\"],\"success\":true}"
--   ["ff",
--   "int",
--   "2",
--   "[(((3 * var0) + (2 * var1)) - 10)]",
--   "(((3 * var0) + (2 * var1)) - 10)"]
--   "linear_combination h1"

-- example (x y : ℚ) (h1 : x*y + 2*x = 1) (h2 : x = y) :
--   x*y = -2*y + 1 :=
-- by test_polyrith
--   "{\"data\":[\"(poly.const 1/1)\",\"(poly.const -2/1)\"],\"success\":true}"
--   ["ff",
--   "rat",
--   "2",
--   "[(((var0 * var1) + (2 * var0)) - 1), (var0 - var1)]",
--   "((var0 * var1) - ((-2 * var1) + 1))"]
--   "linear_combination h1 - 2 * h2"

-- example (x y : ℝ) (h1 : x + 2 = -3) (h2 : y = 10) :
--   -y + 2*x + 4 = -16 :=
-- by test_polyrith
--   "{\"data\":[\"(poly.const 2/1)\",\"(poly.const -1/1)\"],\"success\":true}"
--   ["ff",
--   "real",
--   "2",
--   "[((var1 + 2) - -3), (var0 - 10)]",
--   "(((-var0 + (2 * var1)) + 4) - -16)"]
--   "linear_combination 2 * h1 - h2"

-- example (x y z : ℝ) (ha : x + 2*y - z = 4) (hb : 2*x + y + z = -2)
--     (hc : x + 2*y + z = 2) :
--   -3*x - 3*y - 4*z = 2 :=
-- by test_polyrith
--   "{\"data\":[\"(poly.const 1/1)\",\"(poly.const -1/1)\",\"(poly.const -2/1)\"],\"success\":true}"
--   ["ff",
--   "real",
--   "3",
--   "[(((var0 + (2 * var1)) - var2) - 4), ((((2 * var0) + var1) + var2) - -2), (((var0 + (2 * var1)) + var2) - 2)]",
--   "((((-3 * var0) - (3 * var1)) - (4 * var2)) - 2)"]
--   "linear_combination ha - hb - 2 * hc"

-- example (w x y z : ℝ) (h1 : x + 2.1*y + 2*z = 2) (h2 : x + 8*z + 5*w = -6.5)
--     (h3 : x + y + 5*z + 5*w = 3) :
--   x + 2.2*y + 2*z - 5*w = -8.5 :=
-- by test_polyrith
--   "{\"data\":[\"(poly.const 2/1)\",\"(poly.const 1/1)\",\"(poly.const -2/1)\"],\"success\":true}"
--   ["ff",
--   "real",
--   "4",
--   "[(((var0 + (21/10 * var1)) + (2 * var2)) - 2), (((var0 + (8 * var2)) + (5 * var3)) - -13/2), ((((var0 + var1) + (5 * var2)) + (5 * var3)) - 3)]",
--   "((((var0 + (11/5 * var1)) + (2 * var2)) - (5 * var3)) - -17/2)"]
--   "linear_combination 2 * h1 + h2 - 2 * h3"

-- example (a b c d : ℚ) (h1 : a = 4) (h2 : 3 = b) (h3 : c*3 = d) (h4 : -d = a) :
--   2*a - 3 + 9*c + 3*d = 8 - b + 3*d - 3*a :=
-- by test_polyrith
--   "{\"data\":[\"(poly.const 2/1)\",\"(poly.const -1/1)\",\"(poly.const 3/1)\",\"(poly.const -3/1)\"],\"success\":true}"
--   ["ff",
--   "rat",
--   "4",
--   "[(var0 - 4), (3 - var3), ((var1 * 3) - var2), (-var2 - var0)]",
--   "(((((2 * var0) - 3) + (9 * var1)) + (3 * var2)) - (((8 - var3) + (3 * var2)) - (3 * var0)))"]
--   "linear_combination 2 * h1 - h2 + 3 * h3 - 3 * h4"

-- /-! ### Case with ambiguous identifiers-/

-- example («def evil» y : ℤ) (h1 : 3*«def evil» + 2*y = 10):
--   3*«def evil» + 2*y = 10 :=
-- by test_polyrith
--   "{\"data\":[\"(poly.const 1/1)\"],\"success\":true}"
--   ["ff",
--   "int",
--   "2",
--   "[(((3 * var0) + (2 * var1)) - 10)]",
--   "(((3 * var0) + (2 * var1)) - 10)"]
--   "linear_combination h1"

-- example («¥» y : ℤ) (h1 : 3*«¥» + 2*y = 10):
--   «¥» * (3*«¥» + 2*y) = 10 * «¥» :=
-- by test_polyrith
--   "{\"data\":[\"(poly.var 0)\"],\"success\":true}"
--   ["ff",
--   "int",
--   "2",
--   "[(((3 * var0) + (2 * var1)) - 10)]",
--   "((var0 * ((3 * var0) + (2 * var1))) - (10 * var0))"]
--   "linear_combination «¥» * h1"

-- /-! ### Cases with arbitrary coefficients -/

-- example (a b : ℤ) (h : a = b) :
--   a * a = a * b :=
-- by test_polyrith
--   "{\"data\":[\"(poly.var 0)\"],\"success\":true}"
--   ["ff",
--   "int",
--   "2",
--   "[(var0 - var1)]",
--   "((var0 * var0) - (var0 * var1))"]
--   "linear_combination a * h"

-- example (a b c : ℤ) (h : a = b) :
--   a * c = b * c :=
-- by test_polyrith
--   "{\"data\":[\"(poly.var 1)\"],\"success\":true}"
--   ["ff",
--   "int",
--   "3",
--   "[(var0 - var2)]",
--   "((var0 * var1) - (var2 * var1))"]
--   "linear_combination c * h"

-- example (a b c : ℤ) (h1 : a = b) (h2 : b = 1) :
--   c * a + b = c * b + 1 :=
-- by test_polyrith
--   "{\"data\":[\"(poly.var 0)\",\"(poly.const 1/1)\"],\"success\":true}"
--   ["ff",
--   "int",
--   "3",
--   "[(var1 - var2), (var2 - 1)]",
--   "(((var0 * var1) + var2) - ((var0 * var2) + 1))"]
--   "linear_combination c * h1 + h2"

-- example (x y : ℚ) (h1 : x + y = 3) (h2 : 3*x = 7) :
--   x*x*y + y*x*y + 6*x = 3*x*y + 14 :=
-- by test_polyrith
--   "{\"data\":[\"(poly.mul (poly.var 0) (poly.var 1))\",\"(poly.const 2/1)\"],\"success\":true}"
--   ["ff",
--   "rat",
--   "2",
--   "[((var0 + var1) - 3), ((3 * var0) - 7)]",
--   "(((((var0 * var0) * var1) + ((var1 * var0) * var1)) + (6 * var0)) - (((3 * var0) * var1) + 14))"]
--   "linear_combination x * y * h1 + 2 * h2"

-- example (x y z w : ℚ) (hzw : z = w) : x*z + 2*y*z = x*w + 2*y*w :=
-- by test_polyrith
--   "{\"data\":[\"(poly.add (poly.var 0) (poly.mul (poly.const 2/1) (poly.var 2)))\"],\"success\":true}"
--   ["ff",
--   "rat",
--   "4",
--   "[(var1 - var3)]",
--   "(((var0 * var1) + ((2 * var2) * var1)) - ((var0 * var3) + ((2 * var2) * var3)))"]
--   "linear_combination (x + 2 * y) * hzw"

-- /-! ### Cases with non-hypothesis inputs/input restrictions -/

-- example (a b : ℝ) (ha : 2*a = 4) (hab : 2*b = a - b) (hignore : 3 = a + b) :
--   b = 2 / 3 :=
-- by test_polyrith only [ha, hab]
--   "{\"data\":[\"(poly.const 1/6)\",\"(poly.const 1/3)\"],\"success\":true}"
--   ["ff",
--   "real",
--   "2",
--   "[((2 * var1) - 4), ((2 * var0) - (var1 - var0))]",
--   "(var0 - 2/3)"]
--   "linear_combination ha / 6 + hab / 3"

-- constant term : ∀ a b : ℚ, a + b = 0

-- example (a b c d : ℚ) (h : a + b = 0) (h2: b + c = 0): a + b + c + d = 0 :=
-- by test_polyrith only [term c d, h]
--   "{\"data\":[\"(poly.const 1/1)\",\"(poly.const 1/1)\"],\"success\":true}"
--   ["ff",
--   "rat",
--   "4",
--   "[((var2 + var3) - 0), ((var0 + var1) - 0)]",
--   "((((var0 + var1) + var2) + var3) - 0)"]
--   "linear_combination term c d + h"

-- constants (qc : ℚ) (hqc : qc = 2*qc)

-- example (a b : ℚ) (h : ∀ p q : ℚ, p = q) : 3*a + qc = 3*b + 2*qc :=
-- by test_polyrith [h a b, hqc]
--   "{\"data\":[\"(poly.const 3/1)\",\"(poly.const 1/1)\"],\"success\":true}"
--   ["ff",
--   "rat",
--   "3",
--   "[(var0 - var2), (var1 - (2 * var1))]",
--   "(((3 * var0) + var1) - ((3 * var2) + (2 * var1)))"]
--   "linear_combination 3 * h a b + hqc"

-- constant bad (q : ℚ) : q = 0

-- example (a b : ℚ) : a + b^3 = 0 :=
-- by test_polyrith [bad a, bad (b^2)]
--   "{\"data\":[\"(poly.const 1/1)\",\"(poly.var 1)\"],\"success\":true}"
--   ["ff",
--   "rat",
--   "2",
--   "[(var0 - 0), ((var1 ^ 2) - 0)]",
--   "((var0 + (var1 ^ 3)) - 0)"]
--   "linear_combination bad a + b * bad (b ^ 2)"

-- /-! ### Case over arbitrary field/ring -/

-- example {α} [h : comm_ring α] {a b c d e f : α} (h1 : a*d = b*c) (h2 : c*f = e*d) :
--   c * (a*f - b*e) = 0 :=
-- by test_polyrith
--   "{\"data\":[\"(poly.var 4)\",\"(poly.var 1)\"],\"success\":true}"
--   ["ff",
--   "α",
--   "6",
--   "[((var1 * var5) - (var3 * var0)), ((var0 * var2) - (var4 * var5))]",
--   "((var0 * ((var1 * var2) - (var3 * var4))) - 0)"]
--   "linear_combination e * h1 + a * h2"

-- example {K : Type _} [field K] [invertible 2] [invertible 3]
--   {ω p q r s t x: K} (hp_nonzero : p ≠ 0) (hr : r ^ 2 = q ^ 2 + p ^ 3) (hs3 : s ^ 3 = q + r)
--   (ht : t * s = p) (x : K) (H : 1 + ω + ω ^ 2 = 0) :
--   x ^ 3 + 3 * p * x - 2 * q =
--     (x - (s - t)) * (x - (s * ω - t * ω ^ 2)) * (x - (s * ω ^ 2 - t * ω)) :=
-- begin
--   have hs_nonzero : s ≠ 0,
--   { contrapose! hp_nonzero with hs_nonzero,
--     test_polyrith
--   "{\"data\":[\"(poly.const 0/1)\",\"(poly.const 0/1)\",\"(poly.const -1/1)\",\"(poly.const 0/1)\",\"(poly.var 4)\"],\"success\":true}"
--   ["ff",
--   "K",
--   "6",
--   "[((var1 ^ 2) - ((var2 ^ 2) + (var0 ^ 3))), ((var3 ^ 3) - (var2 + var1)), ((var4 * var3) - var0), (((1 + var5) + (var5 ^ 2)) - 0), (var3 - 0)]",
--   "(var0 - 0)"]
--   "linear_combination -ht + t * hs_nonzero"},
--   have H' : 2 * q = s ^ 3 - t ^ 3,
--   { rw ← mul_left_inj' (pow_ne_zero 3 hs_nonzero),
--     test_polyrith
--   "{\"data\":[\"(poly.const -1/1)\",\"(poly.sub (poly.add (poly.neg (poly.pow (poly.var 1) 3)) (poly.var 0)) (poly.var 3))\",\"(poly.add (poly.add (poly.mul (poly.pow (poly.var 1) 2) (poly.pow (poly.var 2) 2)) (poly.mul (poly.mul (poly.var 1) (poly.var 2)) (poly.var 4))) (poly.pow (poly.var 4) 2))\",\"(poly.const 0/1)\"],\"success\":true}"
--   ["ff",
--   "K",
--   "6",
--   "[((var3 ^ 2) - ((var0 ^ 2) + (var4 ^ 3))), ((var1 ^ 3) - (var0 + var3)), ((var2 * var1) - var4), (((1 + var5) + (var5 ^ 2)) - 0)]",
--   "(((2 * var0) * (var1 ^ 3)) - (((var1 ^ 3) - (var2 ^ 3)) * (var1 ^ 3)))"]
--   "linear_combination -hr + (-s ^ 3 + q - r) * hs3 + (s ^ 2 * t ^ 2 + s * t * p + p ^ 2) * ht"},
--   test_polyrith
--   "{\"data\":[\"(poly.const 0/1)\",\"(poly.const 0/1)\",\"(poly.add (poly.add (poly.sub (poly.add (poly.add (poly.sub (poly.add (poly.sub (poly.mul (poly.var 0) (poly.pow (poly.var 5) 4)) (poly.mul (poly.var 3) (poly.pow (poly.var 5) 4))) (poly.mul (poly.var 4) (poly.pow (poly.var 5) 4))) (poly.mul (poly.var 3) (poly.pow (poly.var 5) 3))) (poly.mul (poly.var 4) (poly.pow (poly.var 5) 3))) (poly.mul (poly.mul (poly.const 3/1) (poly.var 0)) (poly.pow (poly.var 5) 2))) (poly.mul (poly.var 3) (poly.pow (poly.var 5) 2))) (poly.mul (poly.var 4) (poly.pow (poly.var 5) 2))) (poly.mul (poly.mul (poly.const 2/1) (poly.var 0)) (poly.var 5)))\",\"(poly.add (poly.sub (poly.add (poly.sub (poly.sub (poly.add (poly.add (poly.sub (poly.add (poly.sub (poly.sub (poly.add (poly.neg (poly.mul (poly.mul (poly.var 0) (poly.pow (poly.var 3) 2)) (poly.var 5))) (poly.mul (poly.pow (poly.var 3) 3) (poly.var 5))) (poly.mul (poly.mul (poly.var 0) (poly.pow (poly.var 4) 2)) (poly.var 5))) (poly.mul (poly.pow (poly.var 4) 3) (poly.var 5))) (poly.mul (poly.mul (poly.var 0) (poly.var 1)) (poly.pow (poly.var 5) 2))) (poly.mul (poly.mul (poly.var 1) (poly.var 3)) (poly.pow (poly.var 5) 2))) (poly.mul (poly.mul (poly.var 1) (poly.var 4)) (poly.pow (poly.var 5) 2))) (poly.mul (poly.pow (poly.var 0) 2) (poly.var 3))) (poly.pow (poly.var 3) 3)) (poly.mul (poly.pow (poly.var 0) 2) (poly.var 4))) (poly.pow (poly.var 4) 3)) (poly.mul (poly.mul (poly.var 0) (poly.var 1)) (poly.var 5))) (poly.mul (poly.mul (poly.const 3/1) (poly.var 0)) (poly.var 1)))\",\"(poly.const -1/1)\"],\"success\":true}"
--   ["ff",
--   "K",
--   "7",
--   "[((var6 ^ 2) - ((var2 ^ 2) + (var1 ^ 3))), ((var3 ^ 3) - (var2 + var6)), ((var4 * var3) - var1), (((1 + var5) + (var5 ^ 2)) - 0), ((2 * var2) - ((var3 ^ 3) - (var4 ^ 3)))]",
--   "((((var0 ^ 3) + ((3 * var1) * var0)) - (2 * var2)) - (((var0 - (var3 - var4)) * (var0 - ((var3 * var5) - (var4 * (var5 ^ 2))))) * (var0 - ((var3 * (var5 ^ 2)) - (var4 * var5)))))"]
--   "linear_combination (x * ω ^ 4 - s * ω ^ 4 + t * ω ^ 4 - s * ω ^ 3 + t * ω ^ 3 + 3 * x * ω ^ 2 - s * ω ^ 2 +
--       t * ω ^ 2 +
--     2 * x * ω) * ht + (-(x * s ^ 2 * ω) + s ^ 3 * ω - x * t ^ 2 * ω - t ^ 3 * ω + x * p * ω ^ 2 - p * s * ω ^ 2 +
--                 p * t * ω ^ 2 +
--               x ^ 2 * s -
--             s ^ 3 -
--           x ^ 2 * t +
--         t ^ 3 -
--       x * p * ω +
--     3 * x * p) * H - H'"
-- end

-- /-! ## Degenerate cases -/

-- example {K : Type _} [field K] [char_zero K] {s : K} (hs : 3 * s + 1 = 4) : s = 1 :=
-- by test_polyrith
--   "{\"data\":[\"(poly.const 1/3)\"],\"success\":true}"
--   ["ff",
--   "K",
--   "1",
--   "[(((3 * var0) + 1) - 4)]",
--   "(var0 - 1)"]
--   "linear_combination hs / 3"

-- example {x : ℤ} (h1 : x + 4 = 2) : x = -2 :=
-- by test_polyrith
--   "{\"data\":[\"(poly.const 1/1)\"],\"success\":true}"
--   ["ff",
--   "int",
--   "1",
--   "[((var0 + 4) - 2)]",
--   "(var0 - -2)"]
--   "linear_combination h1"

-- example {w : ℚ} (h1 : 3 * w + 1 = 4) : w = 1 :=
-- by test_polyrith
--   "{\"data\":[\"(poly.const 1/3)\"],\"success\":true}"
--   ["ff",
--   "rat",
--   "1",
--   "[(((3 * var0) + 1) - 4)]",
--   "(var0 - 1)"]
--   "linear_combination h1 / 3"

-- example {x : ℤ} (h1 : 2 * x + 3 = x) : x = -3 :=
-- by test_polyrith
--   "{\"data\":[\"(poly.const 1/1)\"],\"success\":true}"
--   ["ff",
--   "int",
--   "1",
--   "[(((2 * var0) + 3) - var0)]",
--   "(var0 - -3)"]
--   "linear_combination h1"

-- example {c : ℚ} (h1 : 4 * c + 1 = 3 * c - 2) : c = -3 :=
-- by test_polyrith
--   "{\"data\":[\"(poly.const 1/1)\"],\"success\":true}"
--   ["ff",
--   "rat",
--   "1",
--   "[(((4 * var0) + 1) - ((3 * var0) - 2))]",
--   "(var0 - -3)"]
--   "linear_combination h1"

-- example (z : ℤ) (h1 : z + 1 = 2) (h2 : z + 2 = 2) : (1 : ℤ) = 2 :=
-- by test_polyrith
--   "{\"data\":[\"(poly.const 1/1)\",\"(poly.const -1/1)\"],\"success\":true}"
--   ["ff",
--   "int",
--   "1",
--   "[((var0 + 1) - 2), ((var0 + 2) - 2)]",
--   "(1 - 2)"]
--   "linear_combination h1 - h2"

-- We comment the following tests so that we don't overwhelm the SageCell API.

/-

/-! ### Standard Cases over ℤ, ℚ, and ℝ -/

example (x y : ℤ) (h1 : 3*x + 2*y = 10):
  3*x + 2*y = 10 :=
by polyrith

example (x y : ℚ) (h1 : x*y + 2*x = 1) (h2 : x = y) :
  x*y = -2*y + 1 :=
by polyrith

-- example (x y : ℝ) (h1 : x + 2 = -3) (h2 : y = 10) :
--   -y + 2*x + 4 = -16 :=
-- by polyrith

-- example (x y z : ℝ) (ha : x + 2*y - z = 4) (hb : 2*x + y + z = -2)
--     (hc : x + 2*y + z = 2) :
--   -3*x - 3*y - 4*z = 2 :=
-- by polyrith

-- example (w x y z : ℝ) (h1 : x + 2.1*y + 2*z = 2) (h2 : x + 8*z + 5*w = -6.5)
--     (h3 : x + y + 5*z + 5*w = 3) :
--   x + 2.2*y + 2*z - 5*w = -8.5 :=
-- by polyrith

example (a b c d : ℚ) (h1 : a = 4) (h2 : 3 = b) (h3 : c*3 = d) (h4 : -d = a) :
  2*a - 3 + 9*c + 3*d = 8 - b + 3*d - 3*a :=
by polyrith

/-! ### Case with ambiguous identifiers-/
-- set_option trace.Meta.Tactic.polyrith true
example («def evil» y : ℤ) (h1 : 3*«def evil» + 2*y = 10):
  3*«def evil» + 2*y = 10 :=
by polyrith

example («¥» y : ℤ) (h1 : 3*«¥» + 2*y = 10):
  «¥» * (3*«¥» + 2*y) = 10 * «¥» :=
by polyrith

/-! ### Cases with arbitrary coefficients -/

example (a b : ℤ) (h : a = b) :
  a * a = a * b :=
by polyrith

example (a b c : ℤ) (h : a = b) :
  a * c = b * c :=
by polyrith

example (a b c : ℤ) (h1 : a = b) (h2 : b = 1) :
  c * a + b = c * b + 1 :=
by polyrith

example (x y : ℚ) (h1 : x + y = 3) (h2 : 3*x = 7) :
  x*x*y + y*x*y + 6*x = 3*x*y + 14 :=
by polyrith

example (x y z w : ℚ) (hzw : z = w) : x*z + 2*y*z = x*w + 2*y*w :=
by polyrith

/-! ### Cases with non-hypothesis inputs/input restrictions -/

-- example (a b : ℝ) (ha : 2*a = 4) (hab : 2*b = a - b) (hignore : 3 = a + b) :
--   b = 2 / 3 :=
-- by polyrith only [ha, hab]

axiom term : ∀ a b : ℚ, a + b = 0

example (a b c d : ℚ) (h : a + b = 0) (h2: b + c = 0): a + b + c + d = 0 :=
by polyrith only [term c d, h]

axiom qc : ℚ
axiom hqc : qc = 2*qc

example (a b : ℚ) (h : ∀ p q : ℚ, p = q) : 3*a + qc = 3*b + 2*qc :=
by polyrith [h a b, hqc]

axiom bad (q : ℚ) : q = 0

example (a b : ℚ) : a + b^3 = 0 :=
by polyrith [bad a, bad (b^2)]

/-! ### Case over arbitrary field/ring -/

example {α} [h : CommRing α] {a b c d e f : α} (h1 : a*d = b*c) (h2 : c*f = e*d) :
  c * (a*f - b*e) = 0 :=
by polyrith

-- example {K : Type _} [Field K] [Invertible 2] [Invertible 3]
--   {ω p q r s t x: K} (hp_nonzero : p ≠ 0) (hr : r ^ 2 = q ^ 2 + p ^ 3) (hs3 : s ^ 3 = q + r)
--   (ht : t * s = p) (x : K) (H : 1 + ω + ω ^ 2 = 0) :
--   x ^ 3 + 3 * p * x - 2 * q =
--     (x - (s - t)) * (x - (s * ω - t * ω ^ 2)) * (x - (s * ω ^ 2 - t * ω)) :=
-- begin
--   have hs_nonzero : s ≠ 0,
--   { contrapose! hp_nonzero with hs_nonzero,
--     polyrith,
--      },
--   have H' : 2 * q = s ^ 3 - t ^ 3,
--   { rw ← mul_left_inj' (pow_ne_zero 3 hs_nonzero),
--     polyrith,},
--   polyrith,
-- end

/-!
### With trace enabled
Here, the tactic will trace the command that gets sent to sage,
and so the tactic will not prove the goal. `linear_combination`
is called manually to prevent errors.
-/

set_option trace.Meta.Tactic.polyrith true

-- example (x y : ℝ) (h1 : x + 2 = -3) (h2 : y = 10) : -y + 2*x + 4 = -16 := by
--   polyrith
--   linear_combination 2 * h1 - h2

example (a b c : ℤ) (h1 : a = b) (h2 : b = 1) : c * a + b = c * b + 1 := by
  polyrith
  linear_combination c * h1 + h2

example (a b c d : ℚ) (h : a + b = 0) (h2: b + c = 0): a + b + c + d = 0 := by
  polyrith only [term c d, h]
  linear_combination term c d + h

example (a b : ℚ) (h : ∀ p q : ℚ, p = q) : 3*a + qc = 3*b + 2*qc := by
  polyrith [h a b, hqc]
  linear_combination 3 * h a b + hqc
-/

-- the following can be uncommented to regenerate the tests above.

/-

/-! ### Standard Cases over ℤ, ℚ, and ℝ -/

example (x y : ℤ) (h1 : 3*x + 2*y = 10):
  3*x + 2*y = 10 :=
by create_polyrith_test

example (x y : ℚ) (h1 : x*y + 2*x = 1) (h2 : x = y) :
  x*y = -2*y + 1 :=
by create_polyrith_test

example (x y : ℝ) (h1 : x + 2 = -3) (h2 : y = 10) :
  -y + 2*x + 4 = -16 :=
by create_polyrith_test

example (x y z : ℝ) (ha : x + 2*y - z = 4) (hb : 2*x + y + z = -2)
    (hc : x + 2*y + z = 2) :
  -3*x - 3*y - 4*z = 2 :=
by create_polyrith_test

example (w x y z : ℝ) (h1 : x + 2.1*y + 2*z = 2) (h2 : x + 8*z + 5*w = -6.5)
    (h3 : x + y + 5*z + 5*w = 3) :
  x + 2.2*y + 2*z - 5*w = -8.5 :=
by create_polyrith_test

example (a b c d : ℚ) (h1 : a = 4) (h2 : 3 = b) (h3 : c*3 = d) (h4 : -d = a) :
  2*a - 3 + 9*c + 3*d = 8 - b + 3*d - 3*a :=
by create_polyrith_test

/-! ### Case with ambiguous identifiers-/

example («def evil» y : ℤ) (h1 : 3*«def evil» + 2*y = 10):
  3*«def evil» + 2*y = 10 :=
by create_polyrith_test

example («¥» y : ℤ) (h1 : 3*«¥» + 2*y = 10):
  «¥» * (3*«¥» + 2*y) = 10 * «¥» :=
by create_polyrith_test

/-! ### Cases with arbitrary coefficients -/

example (a b : ℤ) (h : a = b) :
  a * a = a * b :=
by create_polyrith_test

example (a b c : ℤ) (h : a = b) :
  a * c = b * c :=
by create_polyrith_test

example (a b c : ℤ) (h1 : a = b) (h2 : b = 1) :
  c * a + b = c * b + 1 :=
by create_polyrith_test

example (x y : ℚ) (h1 : x + y = 3) (h2 : 3*x = 7) :
  x*x*y + y*x*y + 6*x = 3*x*y + 14 :=
by create_polyrith_test

example (x y z w : ℚ) (hzw : z = w) : x*z + 2*y*z = x*w + 2*y*w :=
by create_polyrith_test

/-! ### Cases with non-hypothesis inputs/input restrictions -/

example (a b : ℝ) (ha : 2*a = 4) (hab : 2*b = a - b) (hignore : 3 = a + b) :
  b = 2 / 3 :=
by create_polyrith_test only [ha, hab]

constant term : ∀ a b : ℚ, a + b = 0

example (a b c d : ℚ) (h : a + b = 0) (h2: b + c = 0): a + b + c + d = 0 :=
by create_polyrith_test only [term c d, h]

constants (qc : ℚ) (hqc : qc = 2*qc)

example (a b : ℚ) (h : ∀ p q : ℚ, p = q) : 3*a + qc = 3*b + 2*qc :=
by create_polyrith_test [h a b, hqc]

constant bad (q : ℚ) : q = 0

example (a b : ℚ) : a + b^3 = 0 :=
by create_polyrith_test [bad a, bad (b^2)]

/-! ### Case over arbitrary field/ring -/

example {α} [h : comm_ring α] {a b c d e f : α} (h1 : a*d = b*c) (h2 : c*f = e*d) :
  c * (a*f - b*e) = 0 :=
by create_polyrith_test

example {K : Type _} [field K] [invertible 2] [invertible 3]
  {ω p q r s t x: K} (hp_nonzero : p ≠ 0) (hr : r ^ 2 = q ^ 2 + p ^ 3) (hs3 : s ^ 3 = q + r)
  (ht : t * s = p) (x : K) (H : 1 + ω + ω ^ 2 = 0) :
  x ^ 3 + 3 * p * x - 2 * q =
    (x - (s - t)) * (x - (s * ω - t * ω ^ 2)) * (x - (s * ω ^ 2 - t * ω)) :=
begin
  have hs_nonzero : s ≠ 0,
  { contrapose! hp_nonzero with hs_nonzero,
    create_polyrith_test },
  have H' : 2 * q = s ^ 3 - t ^ 3,
  { rw ← mul_left_inj' (pow_ne_zero 3 hs_nonzero),
    create_polyrith_test },
  create_polyrith_test
end

/-! ## Degenerate cases -/

example {K : Type _} [field K] [char_zero K] {s : K} (hs : 3 * s + 1 = 4) : s = 1 :=
by create_polyrith_test

example {x : ℤ} (h1 : x + 4 = 2) : x = -2 :=
by create_polyrith_test

example {w : ℚ} (h1 : 3 * w + 1 = 4) : w = 1 :=
by create_polyrith_test

example {x : ℤ} (h1 : 2 * x + 3 = x) : x = -3 :=
by create_polyrith_test

example {c : ℚ} (h1 : 4 * c + 1 = 3 * c - 2) : c = -3 :=
by create_polyrith_test

example (z : ℤ) (h1 : z + 1 = 2) (h2 : z + 2 = 2) : (1 : ℤ) = 2 :=
by create_polyrith_test

-/

-- example (a b : ℤ) (h : a + b = 4) : a + b = 0 := by
--   fail_if_success polyrith
--   -- polyrith failed to retrieve a solution from Sage!
--   -- ValueError: polynomial is not in the ideal
--   sorry

-- example (a : ℕ) : a = 0 := by
--   have := True.intro
--   polyrith -- polyrith did not find any relevant hypotheses and the goal is not provable by ring