Zulip Chat Archive

Stream: general

Topic: how does tactic.ring work?


Kevin Buzzard (Jun 11 2018 at 08:28):

I have read through programming in Lean a couple of times and now wonder if it's time I started reading something else. I decided to read the ring tactic (not least because it failed to prove something relatively simple the last time I tried to use it and I'd rather fix it myself than pester Mario, not that I have any idea about how far I need to go before I am anywhere close to being able to fix it).

Kevin Buzzard (Jun 11 2018 at 08:28):

Anyway, I started to read it:

Kevin Buzzard (Jun 11 2018 at 08:28):

https://github.com/kbuzzard/mathlib/blob/tactic_doc/docs/ring_tactic.rst

Kevin Buzzard (Jun 11 2018 at 08:28):

There are my comments on the first 30 or so lines, plus a long intro summarising programming in lean

Kevin Buzzard (Jun 11 2018 at 08:29):

There's a link to programming in lean: https://leanprover.github.io/programming_in_lean/programming_in_lean.pdf

Kevin Buzzard (Jun 11 2018 at 08:30):

Here's Patrick's secret gem: https://hanoifabs.files.wordpress.com/2018/05/slides.pdf (thanks Johannes)

Kevin Buzzard (Jun 11 2018 at 08:30):

and that is pretty much every online resource for Lean tactics. Here's the file I want to understand:

Kevin Buzzard (Jun 11 2018 at 08:31):

https://github.com/leanprover/mathlib/blob/master/tactic/ring.lean

Kevin Buzzard (Jun 11 2018 at 08:31):

if anyone wants to help me understand it, they're welcome to edit the rst file. Note: I wrote ring_ractic.rst in sphinx not markdown. It's still human-readable and pretty easy to pick up.

Johan Commelin (Jun 11 2018 at 10:42):

Typo: "especially if one does it within Lean (thus gaining the ability to hover over or click on functions and see their type, definition and so on)." <-- you mean VScode instead of Lean, right?
Another typo: "Note that evem though" <-- s/evem/even/

Kevin Buzzard (Jun 11 2018 at 12:15):

Thanks. I had a look through all of tactic.ring and really it doesn't look too bad. I'm currently reading http://www.cs.ru.nl/~freek/courses/tt-2014/read/10.1.1.61.3041.pdf Assia's take on the matter (which I think was Mario's source for his code)

Kevin Buzzard (Jun 11 2018 at 16:25):

in fact I know it was Mario's source for the code because I've just noticed that it says this in the comments at the top.

Kevin Buzzard (Jun 12 2018 at 09:41):

import tactic.ring
variables (R : Type) [comm_ring R]

example (a b : R) :
  a * (b * b) + (b * (a * a) + (b * (b * b) + (a * (2 * a * b) + b * (2 * a * b)))) =
  b * (b * b) + (3 * a * (b * b) + 3 * (a * a) * b) :=
begin
  ring,
  -- goal now (b * a + (2 * b * a + 3 * b ^ 2)) * a + b ^ 3 = (3 * b * a + 3 * b ^ 2) * a + b ^ 3
  simp,
  -- goal now (b * a + (2 * b * a + 3 * b ^ 2)) * a = (3 * b * a + 3 * b ^ 2) * a
  ring -- works
end

Kevin Buzzard (Jun 12 2018 at 09:41):

elaboration: tactic execution took 2.23s
num. allocated objects:  8522
num. allocated closures: 2163
 2229ms   100.0%   scope_trace
 2229ms   100.0%   tactic.istep._lambda_1
 2229ms   100.0%   tactic.istep
 2229ms   100.0%   tactic.step
 2229ms   100.0%   _interaction._lambda_2
 1987ms    89.1%   interaction_monad_orelse
 1929ms    86.5%   tactic.ring.eval
 1156ms    51.9%   tactic.interactive.ring1
 1156ms    51.9%   tactic.interactive.ring._lambda_1
 1156ms    51.9%   tactic.interactive.ring
 1057ms    47.4%   tactic.ring.eval_mul

Kevin Buzzard (Jun 12 2018 at 09:41):

etc etc

Kevin Buzzard (Jun 12 2018 at 09:41):

This tactic doesn't quite work yet as far as I can see.

Kevin Buzzard (Jun 12 2018 at 09:42):

(noticable pause in ring whilst it's failing to prove it the first time)

Kevin Buzzard (Jun 12 2018 at 09:42):

But I want to fix it.

Kevin Buzzard (Jun 12 2018 at 09:42):

I re-read the "rings done right" paper and I understood _much_ more of it this time around.

Kevin Buzzard (Jun 12 2018 at 09:43):

One insight which dawned on me is that there are two completely different issues involved with writing a ring tactic. Maybe everyone else is aware of this.

Kevin Buzzard (Jun 12 2018 at 09:43):

Let me explain my understanding of what the ring tactic does.

Kevin Buzzard (Jun 12 2018 at 09:43):

Let's say d : int and we want to prove d^2 + 2*d + 1 = (d+1)^2

Kevin Buzzard (Jun 12 2018 at 09:44):

If we want to prove this using ring then we are saying "this is not true because of something specific to int, this is a general fact about rings"

Kevin Buzzard (Jun 12 2018 at 09:44):

So what we actually want to do is to prove that X2+2X+1=(X+1)2X^2+2X+1=(X+1)^2 in Z[X]\mathbb{Z}[X]

Kevin Buzzard (Jun 12 2018 at 09:45):

and then deduce our goal by specialising to X=dX=d

Kevin Buzzard (Jun 12 2018 at 09:45):

This makes it clear what we have to do here.

Kevin Buzzard (Jun 12 2018 at 09:46):

First we need to build a new type in Lean corresponding to polynomials in one variable

Kevin Buzzard (Jun 12 2018 at 09:46):

or more generally, if we want to prove stuff like (a+b)^2=a^2+2*a*b+b^2 for ints a,b

Kevin Buzzard (Jun 12 2018 at 09:46):

we will need polynomials in a finite set of variables.

Kevin Buzzard (Jun 12 2018 at 09:46):

Of course Lean has these.

Kevin Buzzard (Jun 12 2018 at 09:47):

Once we have this new type, we need to prove an "evaluation theorem", saying that if f(X)=g(X)f(X)=g(X) in our polynomial ring

Kevin Buzzard (Jun 12 2018 at 09:47):

then f(d)=g(d)f(d)=g(d) in int

Kevin Buzzard (Jun 12 2018 at 09:48):

Along the way it would be natural to prove things like f(d)+g(d)=(f+g)(d)f(d)+g(d)=(f+g)(d) and so on

Kevin Buzzard (Jun 12 2018 at 09:49):

So we define our new polynomial type, define evaluation, prove nice properties about the evaluation map

Kevin Buzzard (Jun 12 2018 at 09:49):

and now all that's left is the following problem:

Kevin Buzzard (Jun 12 2018 at 09:49):

given d^2+2*d+1 with d : int

Kevin Buzzard (Jun 12 2018 at 09:49):

we need to manufacture X^2+2*X+1 in our polynomial ring type.

Kevin Buzzard (Jun 12 2018 at 09:50):

And for this we really need to take apart d^2+2*d+1 and see how it is built from d and stuff that has analogues in the polynomial ring.

Kevin Buzzard (Jun 12 2018 at 09:50):

(+,^,2,1)

Kevin Buzzard (Jun 12 2018 at 09:51):

and I think that it's at this point that the non-tactic-user gets stuck.

Kevin Buzzard (Jun 12 2018 at 09:56):

@Simon Hudon Assume Lean has a working polynomial ring type ZX (there are some kicking around, but perhaps none in mathlib) representing polynomials with integer coefficients in one variable X. Am I right in thinking that it doesn't even make sense to ask for a function (in Lean's sense) which sends d^2+2*d+1 to X^2+2*X+1 where d : int is some variable? I can't even imagine what the domain of such a function would be. On the other hand, would I be able to write a tactic which took the expression d^2+2*d+1 as an input and spat out X^2+2*X+1?

Kevin Buzzard (Jun 12 2018 at 09:57):

Or is life not even that easy?

Scott Morrison (Jun 12 2018 at 10:34):

certainly given d^2+2*d+1 as an expr (and let's say d is also given, as an expr), in meta land we can construct X^2+2*X+1.

Scott Morrison (Jun 12 2018 at 10:35):

(Mario's implementation does clever things, including a representation of sparse polynomials, but if you just want the stupid version I could probably manage that function.)

Simon Hudon (Jun 12 2018 at 12:04):

Is this simply renaming variables in the goal?

Scott Morrison (Jun 12 2018 at 12:16):

Sorry, the example wasn't very helpful: I just meant, determine if some expression is in fact a polynomial in some other expr, and if so, present it as such in some form (list of coefficients, map of coefficients, etc).

Kevin Buzzard (Jun 12 2018 at 12:47):

Is this simply renaming variables in the goal?

@Simon Hudon No I think it's more. X is a genuine polynomial variable, we have a type ZX with an inclusion from int into ZX and also some element X : ZX which is not in the image of int, it's an abstract polynomial variable

Kevin Buzzard (Jun 12 2018 at 12:47):

@Scott Morrison do we _have_ to do this in meta-land?

Simon Hudon (Jun 12 2018 at 12:48):

ah! i see. So a kind of parser.

Kevin Buzzard (Jun 12 2018 at 12:48):

I can see that Mario's implementation uses "Horner form" of a polynomial, so sparse polys are handled better.

Kevin Buzzard (Jun 12 2018 at 12:48):

Yes, I was interested in building the stupid version of tactic.ring

Simon Hudon (Jun 12 2018 at 12:48):

Yes, I think it has to be in meta because you need access to the expr syntax tree

Kevin Buzzard (Jun 12 2018 at 12:48):

What I see in Mario's file seems to me to be a construction of an abstract polynomial ring but completely in meta-land

Kevin Buzzard (Jun 12 2018 at 12:49):

Here is a concrete question.

Kevin Buzzard (Jun 12 2018 at 12:49):

Would it be possible to just define the polynomial ring Z[X] in "normal" Lean

Kevin Buzzard (Jun 12 2018 at 12:49):

e.g. in a non-efficient way, using lists for coefficients

Simon Hudon (Jun 12 2018 at 12:49):

I believe so

Kevin Buzzard (Jun 12 2018 at 12:49):

and then write a much simpler tactic than tactic/ring.lean

Kevin Buzzard (Jun 12 2018 at 12:50):

which can prove statements of the form "forall d : int, (d+1)^3=d^3+3d^2+3d+1"

Kevin Buzzard (Jun 12 2018 at 12:50):

by temporarily dipping into meta-land to construct the polynomials (X+1)^3 and X^3+3X^2+3X+1

Kevin Buzzard (Jun 12 2018 at 12:50):

and then checking that they're equal in Z[X]

Kevin Buzzard (Jun 12 2018 at 12:50):

and then evaluating at d

Kevin Buzzard (Jun 12 2018 at 12:50):

and deducing the result?

Kevin Buzzard (Jun 12 2018 at 12:51):

Or is it an essential part of the tactic.ring tactic that one builds some version of Z[X] in meta-land?

Kevin Buzzard (Jun 12 2018 at 12:52):

It seems to me that Mario writes horner in normal-land and proves some lemmas about it in normal-land

Kevin Buzzard (Jun 12 2018 at 12:53):

but the key facts are things like eval_add, which is meta

Kevin Buzzard (Jun 12 2018 at 12:57):

eval_add seems to be some sort of theorem of the form "if this expr represents f and this expr represents g, then I will return an expr plus a proof that it is the evaluation of f plus the evaluation of g"

Kevin Buzzard (Jun 12 2018 at 12:58):

I was wondering whether one could instead use a non-expr version

Simon Hudon (Jun 12 2018 at 12:58):

Because expr is in meta land, you can't do any of that stuff in non-meta land

Kevin Buzzard (Jun 12 2018 at 12:58):

right

Kevin Buzzard (Jun 12 2018 at 12:58):

but if I had a tactic which took d^2+2*d+1

Kevin Buzzard (Jun 12 2018 at 12:59):

and returned X^2+2*X+1

Simon Hudon (Jun 12 2018 at 12:59):

I see ok yes that's possible

Simon Hudon (Jun 12 2018 at 12:59):

That's called a proof by reflection

Kevin Buzzard (Jun 12 2018 at 12:59):

plus a proof that X^2+X+1 evaluated at d was d^2+2*d+1

Kevin Buzzard (Jun 12 2018 at 12:59):

then it seems to me that there's a chance that I can prove d^2+2*d+1=(d+1)^2 using this tactic

Kevin Buzzard (Jun 12 2018 at 13:00):

because I feed in both sides to the tactic

Kevin Buzzard (Jun 12 2018 at 13:00):

prove they're equal in ZX

Kevin Buzzard (Jun 12 2018 at 13:00):

and deduce that their valuations are equal

Kevin Buzzard (Jun 12 2018 at 13:00):

I am trying to get as much of the proof out of meta-land as I can

Reid Barton (Jun 12 2018 at 13:01):

Regarding lands, have you looked at the slides on metaprogramming https://hanoifabs.files.wordpress.com/2018/05/slides.pdf?

Reid Barton (Jun 12 2018 at 13:01):

In particular page 4

Kevin Buzzard (Jun 12 2018 at 13:01):

This is part of the reason I'm thinking about this now

Kevin Buzzard (Jun 12 2018 at 13:02):

I have looked through all of tactic/ring.lean and all of a sudden it doesn't look as intimidating as it used ti

Kevin Buzzard (Jun 12 2018 at 13:02):

to

Kevin Buzzard (Jun 12 2018 at 13:04):

https://github.com/kbuzzard/mathlib/blob/ring_tactic_comments/tactic/ring.lean

Reid Barton (Jun 12 2018 at 13:04):

If you don't need access to meta-land features like general recursion or expr then you can stay in normal-land; and surely the theory of Z[X]\mathbb{Z}[X] doesn't need these things. On the other hand, you may want to avoid using noncomputable things if you want your tactic to, for example, be able to decide whether two polynomials in Z[X]\mathbb{Z}[X] are equal so that it can decide whether to succeed or fail.

Kevin Buzzard (Jun 12 2018 at 13:04):

I am trying to write a comment about every definition and theorem in that link

Kevin Buzzard (Jun 12 2018 at 13:05):

So one can certainly make Z[X], indeed it's been done several times although I don't think it's in mathlib yet

Kevin Buzzard (Jun 12 2018 at 13:05):

The problem is the function sending d^2+2d+1 to X^2+2X+1

Reid Barton (Jun 12 2018 at 13:06):

Right, so I guess the approach used here by ring is to represent a variable (which is basically something the tactic can't break down into further ring operations) by its expr

Kevin Buzzard (Jun 12 2018 at 13:07):

right

Reid Barton (Jun 12 2018 at 13:07):

Here the variable is simply d, but it could have been more complicated, e.g. sin t

Kevin Buzzard (Jun 12 2018 at 13:07):

right

Kevin Buzzard (Jun 12 2018 at 13:07):

or there could be several variables

Reid Barton (Jun 12 2018 at 13:09):

So that must be why the destruct_ty thing is in meta, though if you wanted it to be in normal-land, you could probably just parameterize it on the expression type.

Reid Barton (Jun 12 2018 at 13:09):

BTW, _ty probably just stands for "type"

Kevin Buzzard (Jun 12 2018 at 13:11):

So completely independent of the ring tactic

Kevin Buzzard (Jun 12 2018 at 13:11):

there is this horner thing

Kevin Buzzard (Jun 12 2018 at 13:11):

and what seems to be going on there

Kevin Buzzard (Jun 12 2018 at 13:11):

is that (and this is Assia's insight, or her joint insight with her co-author)

Kevin Buzzard (Jun 12 2018 at 13:11):

storing polynomials as lists of coefficients might suck

Kevin Buzzard (Jun 12 2018 at 13:12):

especially if you want to work out x^100 * x^100 without doing 10000 computations

Kevin Buzzard (Jun 12 2018 at 13:12):

so they store e.g. x^100+3x^2+7 as (1*x^98+3)*x^2+7

Kevin Buzzard (Jun 12 2018 at 13:12):

iterating the "x maps to a*x^n+b" map

Kevin Buzzard (Jun 12 2018 at 13:13):

and so this is some sort of normal form for polynomials

Kevin Buzzard (Jun 12 2018 at 13:13):

which we could call "horner normal form"

Kevin Buzzard (Jun 12 2018 at 13:13):

and if you store polynomials in this way then it's a PITA to add or multiply them

Kevin Buzzard (Jun 12 2018 at 13:13):

but this is OK because somehow this isn't the bottleneck

Reid Barton (Jun 12 2018 at 13:14):

I see, and eval_add is basically implementing addition of polynomials in this form, it looks like?

Kevin Buzzard (Jun 12 2018 at 13:14):

So one could envisage writing a second ring tactic which (a) was far less efficient and (b) worked in some situations where Mario's doesn't (because Mario's is currently buggy)

Kevin Buzzard (Jun 12 2018 at 13:14):

where you just use lists for coefficients

Kevin Buzzard (Jun 12 2018 at 13:15):

and then the resulting tactic file would have this extra obfuscating layer of difficulty removed

Kevin Buzzard (Jun 12 2018 at 13:15):

and this was what got me into wondering whether I could even just use one of the already-existing polynomial ring Lean implementations

Kevin Buzzard (Jun 12 2018 at 13:15):

instead of making Z[X] in meta-land

Kevin Buzzard (Jun 12 2018 at 13:17):

I see, and eval_add is basically implementing addition of polynomials in this form, it looks like?

Yes, it's perhaps doing something clever like not just implementing addition, it's also collecting the proofs that addition commutes with evaluation, but the five lemmas before eval_add are precisely the five lemmas you need to add polynomials in "Horner form"

Kevin Buzzard (Jun 12 2018 at 13:18):

You add ax^n+b and a'x^n'+b'

Kevin Buzzard (Jun 12 2018 at 13:18):

where a and a' are allowed to be polynomials in horner form

Kevin Buzzard (Jun 12 2018 at 13:18):

and you have to do the three cases n<n', n=n', n>n'

Kevin Buzzard (Jun 12 2018 at 13:18):

and then also you have to add ax^n+b to c where c is a constant

Kevin Buzzard (Jun 12 2018 at 13:18):

both ways around

Kevin Buzzard (Jun 12 2018 at 13:18):

and there's some implicit inductive type horner_form

Kevin Buzzard (Jun 12 2018 at 13:19):

which is defined by: a constant is in horner_form, and if a is in horner_form then so is a*x^n+b

Kevin Buzzard (Jun 12 2018 at 13:19):

and then every polynomial has a canonical horner form

Kevin Buzzard (Jun 12 2018 at 13:19):

and also perhaps some non-canonical ones

Kevin Buzzard (Jun 12 2018 at 13:22):

I haven't got down as far as normalize but this might be the function which puts something in horner form into its normalised state (which you need because you need an algorithm for figuring out when two polynomials are equal)

Reid Barton (Jun 12 2018 at 13:25):

and this was what got me into wondering whether I could even just use one of the already-existing polynomial ring Lean implementations

I don't see why not. Probably you can even use the one in linear_algebra.multivariate_polynomial

Kevin Buzzard (Jun 12 2018 at 13:29):

You understand that I am not looking for some sort of uber-efficient ring tactic, like the one Mario wrote. I am trying to see in some sense what the minimal amount of work would be, if I wanted to write a more inefficient ring tactic of my own

Kevin Buzzard (Jun 12 2018 at 13:29):

and the more I can get out of meta-land the better

Kevin Buzzard (Jun 12 2018 at 13:29):

Even for just equations involving one unknown, I would be interested

Kevin Buzzard (Jun 12 2018 at 13:30):

not least because example (d : ℕ) : d^2+2*d+1=(d+1)^2 := by ring currently fails and rather than pestering Mario I thought it would be an interesting exercise to try and work out why.

Reid Barton (Jun 12 2018 at 13:31):

Yes, I think a ring tactic optimized for simplicity would be valuable as a demonstration of how to write similar tactics, as well.

Kevin Buzzard (Jun 12 2018 at 13:43):

Well maybe that's where this thread is going.

Mario Carneiro (Jun 12 2018 at 13:47):

I'm not sure you can actually save that much work with a dumber ring tactic

Mario Carneiro (Jun 12 2018 at 13:48):

Probably using dense polynomial representation is a bit easier, but I don't think proof by reflection is easier (although more of it can be verified)

Mario Carneiro (Jun 12 2018 at 13:48):

but precisely because more of it is verified, there is more work to do

Mario Carneiro (Jun 12 2018 at 13:49):

If expr was not meta, almost all of the ring tactic could be non-meta

Simon Hudon (Jun 12 2018 at 13:50):

Do you know if there's any plan to make expr non-meta?

Mario Carneiro (Jun 12 2018 at 13:51):

I have not investigated the ring bug, but one way to find out what is happening is to insert type checks in eval_add and such

Reid Barton (Jun 12 2018 at 13:51):

By "simple", I really mean "easy to understand"

Reid Barton (Jun 12 2018 at 13:51):

not necessarily short

Mario Carneiro (Jun 12 2018 at 13:51):

There is no plan to make expr non-meta, and in fact I attempted such a plan and was rebuffed several months ago

Mario Carneiro (Jun 12 2018 at 13:52):

The likely alternative is to have a mirror copy of expr that is non-meta

Mario Carneiro (Jun 12 2018 at 13:52):

which would have to avoid certain meta things like macros

Kevin Buzzard (Jun 12 2018 at 14:01):

I'm not sure you can actually save that much work with a dumber ring tactic

Yes, as Reid says, I'm not worried about work, I'm attempting to understand tactics in a way other than "read Programming In Lean again".

Kevin Buzzard (Jun 12 2018 at 14:02):

The only other way I can think of is "read some tactic code and see if you can understand it, and see what questions arise because of it"

Kevin Buzzard (Jun 12 2018 at 14:02):

and that's why I find myself in this thread

Kevin Buzzard (Jun 12 2018 at 14:03):

One question: which is best? Documenting ring.lean like this https://github.com/kbuzzard/mathlib/blob/ring_tactic_comments/tactic/ring.lean

Kevin Buzzard (Jun 12 2018 at 14:03):

or writing a stand-alone file with comments like this https://github.com/kbuzzard/mathlib/blob/tactic_doc/docs/ring_tactic.rst

Kevin Buzzard (Jun 12 2018 at 14:03):

I currently find myself doing both

Kevin Buzzard (Jun 12 2018 at 14:03):

As long as I get to the bottom using one method, I am sure I will have learnt a lot

Kevin Buzzard (Jun 12 2018 at 14:04):

Currently the "adding comments to ring.lean" approach is winning

Kevin Buzzard (Jun 12 2018 at 14:05):

but the waffle above about writing a simpler version -- which to be honest could I think turn into a great tutorial on how to write tactics, if we implement polynomials in one variable using some dumb list method or, even better, perhaps using some already-implemented method

Kevin Buzzard (Jun 12 2018 at 14:06):

Maybe that's the goal of this thread. To write an as-stupid-as-possible ring tactic which attempts to have as little in meta-land as possible, and then stick it up on my blog as some sort of tactic tutorial as an alternative for people to read

Kevin Buzzard (Jun 12 2018 at 14:07):

Next term I'll be supervising a Masters project on how to write tactics so I'd better get my act together and learn it myself

Kevin Buzzard (Jun 12 2018 at 14:07):

The student in question is currently doing an internship at INRIA learning how to do it in Coq so I'm hoping that they will learn quickly and then teach me

Mario Carneiro (Jun 12 2018 at 15:01):

Maybe this will help: There is an implicit inductive type horner_form_expr with the following definition:

meta inductive horner_form_expr : Type
| const : expr → horner_form_expr
| horner (a : horner_form_expr) (x : expr) (n : nat) (b : horner_form_expr) : horner_form_expr

The job of eval_add and the other definitions is to rewrite any expr into a horner_form_expr. However, since horner_form_expr can be represented as an expr, the actual inductive type is omitted to avoid the overhead of converting back and forth. Furthermore, there is a normal form requirement, that says that the x expression must be lex_lt less than any other expressions in x slots of the b subtree.

Mario Carneiro (Jun 12 2018 at 15:03):

destruct is effectively the cases_on for this inductive type

Kevin Buzzard (Jun 12 2018 at 15:38):

Right -- I had basically figured this out. But you see, in some educational blog post about this stuff you could put this type in, and furthermore make it work in a more stupid way using lists of coefficients. What I am still not clear about is whethet you can get away with making it not meta (and hence get away with not actually writing it at all, because it's already written)

Kevin Buzzard (Jun 12 2018 at 15:39):

Because you're in meta-land you can just not even define the type, you can destruct it _assuming_ that it's in this form, and if it's not then big deal, things have gone wrong, just return none

Mario Carneiro (Jun 12 2018 at 15:40):

You can't make it non-meta and still retain the x payloads, which have to be kept as is as exprs

Kevin Buzzard (Jun 12 2018 at 15:41):

My idea was to have a meta function sending d^2+2d+1 to X^2+2X+1

Mario Carneiro (Jun 12 2018 at 15:41):

unless maybe you write them down somewhere else and only keep pointers to them in your non-meta data structure (i.e. indexes into a list of exprs)

Mario Carneiro (Jun 12 2018 at 15:42):

which I guess is similar to your Z[X] suggestion

Kevin Buzzard (Jun 12 2018 at 15:42):

The tactic would only work for goals with one unknown

Mario Carneiro (Jun 12 2018 at 15:42):

but you have to remember these are multivariate polynomials

Kevin Buzzard (Jun 12 2018 at 15:42):

I know yours are

Kevin Buzzard (Jun 12 2018 at 15:42):

but I am suggesting writing a simplified version

Kevin Buzzard (Jun 12 2018 at 15:43):

I want to isolate the "now here we have to write some meta stuff" and make it as small as possible

Mario Carneiro (Jun 12 2018 at 15:43):

If you take d as an input, then there are lots of bad exprs now

Mario Carneiro (Jun 12 2018 at 15:43):

In my approach, there aren't any bad exprs because anything it doesn't understand becomes a new atom

Kevin Buzzard (Jun 12 2018 at 15:44):

I know your approach is better at getting things done

Kevin Buzzard (Jun 12 2018 at 15:44):

I am happy to let both d and Z be inputs

Mario Carneiro (Jun 12 2018 at 15:44):

but you want to have only one atom

Kevin Buzzard (Jun 12 2018 at 15:44):

right

Kevin Buzzard (Jun 12 2018 at 15:44):

and I want to store polynomials as lists

Kevin Buzzard (Jun 12 2018 at 15:44):

these facts are not unrelated

Mario Carneiro (Jun 12 2018 at 15:44):

what happens if I pass d^2+x to your function?

Kevin Buzzard (Jun 12 2018 at 15:45):

my function will fail

Kevin Buzzard (Jun 12 2018 at 15:45):

because my function is there to teach people how to write tactics

Kevin Buzzard (Jun 12 2018 at 15:45):

not to actually be used in the wild

Kevin Buzzard (Jun 12 2018 at 15:45):

I am writing code for a completely different reason to probably any code you ever wrote

Kevin Buzzard (Jun 12 2018 at 15:45):

I am writing code to teach my students that tactics are not scary

Kevin Buzzard (Jun 12 2018 at 15:46):

which is not the impression you get when reading PIL

Mario Carneiro (Jun 12 2018 at 15:46):

Okay, let me think about this

Mario Carneiro (Jun 12 2018 at 15:46):

In the mean time, the ring bug has been fixed.

Kevin Buzzard (Jun 12 2018 at 15:47):

:D

Kevin Buzzard (Jun 12 2018 at 15:47):

OK so forget this thread, main goal achieved ;-)

Mario Carneiro (Jun 12 2018 at 15:47):

The problem was in horner_add_horner_lt (and gt). Suppose we are adding (a1 * x^n1 + b1) + (a2 * x^n2 + b2) where n1 > n2

Kevin Buzzard (Jun 12 2018 at 15:47):

dammit I even looked at that function!

Mario Carneiro (Jun 12 2018 at 15:47):

It's funny, the theorem is not wrong

Mario Carneiro (Jun 12 2018 at 15:48):

but it doesn't normalize like it should

Kevin Buzzard (Jun 12 2018 at 15:48):

in fact that's exactly the point I'm up to

Kevin Buzzard (Jun 12 2018 at 15:48):

/-- This non-meta theorem just says a₁x^n₁+b₁+a₂x^(n₁+k)+b₂=(a₂x^k+a₁)x^n₁+(b₁+b₂) -/

Andrew Ashworth (Jun 12 2018 at 15:49):

I think this is a great idea. It took me some time to understand reflection in Lean. Unfortunately, translating Chlipala's section on it (in CPDT) from Coq to Lean is quite difficult. So a "Lean-first" tutorial would be great.

Mario Carneiro (Jun 12 2018 at 15:49):

The current implementation normalizes b1 + b2 = b', calculates k such that n2 + k = n1, and then outputs the normal form (a1 * x^k + a2) * x^n2 + b'

Kevin Buzzard (Jun 12 2018 at 15:50):

yeah (modulo the fact that the (1,2) notation is switched in this thread from the conventions used in the actual code)

Mario Carneiro (Jun 12 2018 at 15:50):

However, a1 * x^k + a2 is not necessarily in normal form

Kevin Buzzard (Jun 12 2018 at 15:50):

because x might not be lt the monomials showing up in a1?

Mario Carneiro (Jun 12 2018 at 15:51):

Yes. In particular, x might appear in a2

Kevin Buzzard (Jun 12 2018 at 15:51):

or a1

Kevin Buzzard (Jun 12 2018 at 15:51):

;-)

Mario Carneiro (Jun 12 2018 at 15:51):

that's okay

Kevin Buzzard (Jun 12 2018 at 15:52):

not when you switch it so your algebra is correct ;-)

Kevin Buzzard (Jun 12 2018 at 15:52):

[you only did half the editing job]

Mario Carneiro (Jun 12 2018 at 15:52):

the whole point of factorizing a1 * x^k + a2 is that a2 has no x's and a1 has all the high order terms

Kevin Buzzard (Jun 12 2018 at 15:53):

a2 * x^k + a1

Kevin Buzzard (Jun 12 2018 at 15:53):

I think this part of the thread is now beyond saving

Kevin Buzzard (Jun 12 2018 at 15:54):

but I think we both know what the other is saying :-)

Mario Carneiro (Jun 12 2018 at 15:55):

fixed

Mario Carneiro (Jun 12 2018 at 15:55):

I actually noticed the problem in gt, but I was translating to the symmetric version and got confused

Mario Carneiro (Jun 12 2018 at 15:56):

(in this thread, that's not the bug)

Kevin Buzzard (Jun 12 2018 at 15:57):

[I posted my docstring for lt, but you are talking about gt, so now all is right with the world]

Kevin Buzzard (Jun 12 2018 at 15:57):

Right, so let's focus on gt

Mario Carneiro (Jun 12 2018 at 15:57):

the bug is that since both a1 and a2 can contain x, we have a separate subproblem now, to normalize (a1 * x^k + 0) + a2 = a' and then output a' * x^n2+ b'

Kevin Buzzard (Jun 12 2018 at 15:57):

/-- This non-meta theorem just says a₁x^(n₂+k)+b₁+a₂x^n₂+b₂=(a₁x^k+a₂)x^n₂+(b₁+b₂) -/

Mario Carneiro (Jun 12 2018 at 15:58):

so I did that and now it works

Mario Carneiro (Jun 12 2018 at 15:58):

I'm compiling now, I'll post it soon

Kevin Buzzard (Jun 12 2018 at 15:58):

I would have liked to find this bug

Kevin Buzzard (Jun 12 2018 at 15:59):

Because you're in meta mode you don't have to be super-anal about making sure everything is in canonical form

Kevin Buzzard (Jun 12 2018 at 15:59):

you just write procedural code which is supposed to do it

Kevin Buzzard (Jun 12 2018 at 16:00):

@Andrew Ashworth I did the introductory compiler exercise in CPDT, in Lean, over the weekend.

Mario Carneiro (Jun 12 2018 at 16:00):

I'll help you with a tutorial ring tactic later

Mario Carneiro (Jun 12 2018 at 16:01):

But one thing to be careful about is if you do too much non-meta, you might actually end up writing a tactic that does proof by reflection which is a completely different method

Kevin Buzzard (Jun 12 2018 at 16:01):

reflection is _different_? I thought that it was somehow some fundamental principle which was used everywhere?

Mario Carneiro (Jun 12 2018 at 16:01):

ring is an example of how to write tactics that build proofs by induction, but it's hard to do that non-meta

Mario Carneiro (Jun 12 2018 at 16:04):

One way to see the difference is in the proof: A tactic that does proofs by meta-induction produces proofs that get longer as the theorem gets harder to prove, but a proof by reflection is relatively short, with the generated proof being proportional to the statement in length

Mario Carneiro (Jun 12 2018 at 16:05):

Proofs by reflection are characterized by a "heavy" rfl proof somewhere in the middle

Mario Carneiro (Jun 12 2018 at 16:05):

ring produces no heavy steps, every single theorem applied exactly matches the type it should have

Mario Carneiro (Jun 12 2018 at 16:06):

so the kernel never has to do any definitional reduction

Kevin Buzzard (Jun 12 2018 at 16:16):

This is all very instructive and quite different from the PIL stuff which, inevitably, is skewed towards CS applications

Mario Carneiro (Jun 12 2018 at 17:33):

@Kevin Buzzard Here's a toy version of ring that works using computational reflection:

import tactic.basic data.num.lemmas

namespace ring_tac
open tactic

@[derive has_reflect]
inductive ring_expr : Type
| add : ring_expr → ring_expr → ring_expr
| mul : ring_expr → ring_expr → ring_expr
| const : znum → ring_expr
| X : ring_expr

meta def reflect_expr (X : expr) : expr → option ring_expr
| `(%%e₁ + %%e₂) := do
  p₁ ← reflect_expr e₁,
  p₂ ← reflect_expr e₂,
  return (ring_expr.add p₁ p₂)
| `(%%e₁ * %%e₂) := do
  p₁ ← reflect_expr e₁,
  p₂ ← reflect_expr e₂,
  return (ring_expr.mul p₁ p₂)
| e := if e = X then return ring_expr.X else
  do n ← expr.to_int e,
     return (ring_expr.const (znum.of_int' n))

def poly := list znum

def poly.add : poly → poly → poly := λ _ _, []
def poly.mul : poly → poly → poly := λ _ _, []
def poly.const : znum → poly := sorry
def poly.X : poly := sorry

def to_poly : ring_expr → poly
| (ring_expr.add e₁ e₂) := (to_poly e₁).add (to_poly e₂)
| (ring_expr.mul e₁ e₂) := (to_poly e₁).mul (to_poly e₂)
| (ring_expr.const z) := poly.const z
| ring_expr.X := poly.X

def poly.eval {α} [comm_ring α] (X : α) : poly → α
| [] := 0
| (n::l) := n + X * poly.eval l

@[simp] theorem poly.eval_add {α} [comm_ring α] (X : α) : ∀ p₁ p₂ : poly,
  (p₁.add p₂).eval X = p₁.eval X + p₂.eval X := sorry

@[simp] theorem poly.eval_mul {α} [comm_ring α] (X : α) : ∀ p₁ p₂ : poly,
  (p₁.mul p₂).eval X = p₁.eval X * p₂.eval X := sorry

@[simp] theorem poly.eval_const {α} [comm_ring α] (X : α) : ∀ n : znum,
  (poly.const n).eval X = n := sorry

@[simp] theorem poly.eval_X {α} [comm_ring α] (X : α) : poly.X.eval X = X := sorry

def ring_expr.eval {α} [comm_ring α] (X : α) : ring_expr → α
| (ring_expr.add e₁ e₂) := e₁.eval + e₂.eval
| (ring_expr.mul e₁ e₂) := e₁.eval * e₂.eval
| (ring_expr.const z) := z
| ring_expr.X := X

theorem to_poly_eval {α} [comm_ring α] (X : α) (e) : (to_poly e).eval X = e.eval X :=
by induction e; simp [to_poly, ring_expr.eval, *]

theorem main_thm {α} [comm_ring α] (X : α) (e₁ e₂) {x₁ x₂}
  (H : to_poly e₁ = to_poly e₂) (R1 : e₁.eval X = x₁) (R2 : e₂.eval X = x₂) : x₁ = x₂ :=
by rw [← R1, ← R2, ← to_poly_eval, H, to_poly_eval]

meta def ring_tac (X : pexpr) : tactic unit := do
  X ← to_expr X,
  `(%%x₁ = %%x₂) ← target,
  r₁ ← reflect_expr X x₁,
  r₂ ← reflect_expr X x₂,
  let e₁ : expr := reflect r₁,
  let e₂ : expr := reflect r₂,
  `[refine main_thm %%X %%e₁ %%e₂ rfl _ _],
  all_goals `[simp only [ring_expr.eval,
    znum.cast_pos, znum.cast_neg, znum.cast_zero',
    pos_num.cast_bit0, pos_num.cast_bit1,
    pos_num.cast_one']]

example (x : ℤ) : (x + 1) * (x + 1) = x*x+2*x+1 :=
by do ring_tac ```(x)

end ring_tac

Mario Carneiro (Jun 12 2018 at 17:34):

I have left the exercise of defining poly.add, poly.mul, poly.const and poly.X, and proving correctness of the functions in the eval_* theorems (all non-meta), to you.

Mario Carneiro (Jun 12 2018 at 17:35):

Here the "heavy rfl" step is the rfl proof in main_thm

Mario Carneiro (Jun 12 2018 at 17:36):

you will need the mathlib update that just appeared

Kevin Buzzard (Jun 12 2018 at 19:47):

wooah many thanks Mario!

Kevin Buzzard (Jun 12 2018 at 20:11):

example (a b : int) : (a+b)^11=a^11 + 11*b*a^10 + 55*b^2*a^9 + 165*b^3*a^8 + 330*b^4*a^7 + 462*b^5*a^6 + 462*b^6*a^5 + 330*b^7*a^4 + 165*b^8*a^3 + 55*b^9*a^2 + 11*b^10*a + b^11:= by ring

Kevin Buzzard (Jun 12 2018 at 20:11):

but 12 times out :-)

Kevin Buzzard (Jun 12 2018 at 20:11):

[this is of course the official ring, not the one above]

Kevin Buzzard (Jun 12 2018 at 20:12):

example (a b : int) : (a+b)^12=a^12 + 12*b*a^11 + 66*b^2*a^10 + 220*b^3*a^9 + 495*b^4*a^8 + 792*b^5*a^7 + 924*b^6*a^6 + 792*b^7*a^5 + 495*b^8*a^4 + 220*b^9*a^3 + 66*b^10*a^2 + 12*b^11*a + b^12:= by ring -- deterministic timeout

Johan Commelin (Jun 12 2018 at 20:31):

@Kevin Buzzard So you need to teach ring about Chris's binomial theorem!

Kevin Buzzard (Jun 12 2018 at 22:07):

Well, I'm not sure I would use Lean to check the binomial theorem for n=12 :-)

Kevin Buzzard (Jun 12 2018 at 22:08):

Ironically I fired up pari-gp and computed (a+b)^12 in a gazillionth of a second and then cut and pasted the output into Lean in order to see if it could do something which I already had a much better tool for.

Kevin Buzzard (Jun 12 2018 at 23:06):

import tactic.basic data.num.lemmas analysis.real

namespace ring_tac
open tactic

-- why this line?
@[derive has_reflect]
inductive ring_expr : Type
| add : ring_expr  ring_expr  ring_expr
| mul : ring_expr  ring_expr  ring_expr
| const : znum  ring_expr
| X : ring_expr

meta def reflect_expr (X : expr) : expr  option ring_expr
| `(%%e₁ + %%e₂) := do
  p₁  reflect_expr e₁,
  p₂  reflect_expr e₂,
  return (ring_expr.add p₁ p₂)
| `(%%e₁ * %%e₂) := do
  p₁  reflect_expr e₁,
  p₂  reflect_expr e₂,
  return (ring_expr.mul p₁ p₂)
| e := if e = X then return ring_expr.X else
  do n  expr.to_int e,
     return (ring_expr.const (znum.of_int' n))

-- mathlib/data/num has znum and stuff like znum.of_int' (see above)
def poly := list znum
-- but why use it?

def poly.add : poly  poly  poly
| [] g := g
| f [] := f
| (a :: f') (b :: g') := (a + b) :: poly.add f' g'

@[simp] lemma poly.zero_add (p : poly) : poly.add [] p = p := by induction p;refl

def poly.smul : znum  poly  poly
| _ [] := []
| z (a :: f') := (z * a) :: poly.smul z f'

def poly.mul : poly  poly  poly
| [] _ := []
| (a :: f') g := poly.add (poly.smul a g) (0 :: (poly.mul f' g))

def poly.const : znum  poly := λ z, [z]

def poly.X : poly := [0,1]

def to_poly : ring_expr  poly
| (ring_expr.add e₁ e₂) := (to_poly e₁).add (to_poly e₂)
| (ring_expr.mul e₁ e₂) := (to_poly e₁).mul (to_poly e₂)
| (ring_expr.const z) := poly.const z
| ring_expr.X := poly.X

def poly.eval {α} [comm_ring α] (X : α) : poly  α
| [] := 0
| (n::l) := n + X * poly.eval l

@[simp] lemma poly.eval_zero {α} [comm_ring α] (X : α) : poly.eval X [] = 0 := rfl

@[simp] theorem poly.eval_add {α} [comm_ring α] (X : α) :  p₁ p₂ : poly,
  (p₁.add p₂).eval X = p₁.eval X + p₂.eval X :=
begin
  intro p₁,
  induction p₁ with h₁ t₁ H,
    -- base case
    intros,simp [poly.eval],
  -- inductive step
  intro p₂,
  cases p₂ with h₂ t₂,
    simp [poly.add],
  unfold poly.eval poly.add,
  rw (H t₂),
  simp [mul_add]
end

@[simp] lemma poly.eval_mul_zero {α} [comm_ring α] (f : poly) (X : α) :
  poly.eval X (poly.mul f []) = 0 :=
begin
  induction f with h t H,
    refl,
  unfold poly.mul poly.smul poly.add poly.mul poly.eval,
  rw H,simp
end

@[simp] lemma poly.eval_smul {α} [comm_ring α] (X : α) (z : znum) (f : poly) :
  poly.eval X (poly.smul z f) = z * poly.eval X f :=
begin
  induction f with h t H, simp [poly.smul,poly.eval,mul_zero],
  unfold poly.smul poly.eval,
  rw H,
  simp [mul_add,znum.cast_mul,mul_assoc,mul_comm]
end

@[simp] theorem poly.eval_mul {α} [comm_ring α] (X : α) :  p₁ p₂ : poly,
  (p₁.mul p₂).eval X = p₁.eval X * p₂.eval X :=
begin
  intro p₁,induction p₁ with h₁ t₁ H,
    simp [poly.mul],
  intro p₂,
  unfold poly.mul,
  rw poly.eval_add,
  unfold poly.eval,
  rw [H p₂,znum.cast_zero,zero_add,add_mul,poly.eval_smul,mul_assoc]
end


@[simp] theorem poly.eval_const {α} [comm_ring α] (X : α) :  n : znum,
  (poly.const n).eval X = n :=
begin
  intro n,
  unfold poly.const poly.eval,simp
end

@[simp] theorem poly.eval_X {α} [comm_ring α] (X : α) : poly.X.eval X = X :=
begin
  unfold poly.X poly.eval,simp
end


def ring_expr.eval {α} [comm_ring α] (X : α) : ring_expr  α
| (ring_expr.add e₁ e₂) := e₁.eval + e₂.eval
| (ring_expr.mul e₁ e₂) := e₁.eval * e₂.eval
| (ring_expr.const z) := z
| ring_expr.X := X

theorem to_poly_eval {α} [comm_ring α] (X : α) (e) : (to_poly e).eval X = e.eval X :=
by induction e; simp [to_poly, ring_expr.eval, *]

theorem main_thm {α} [comm_ring α] (X : α) (e₁ e₂) {x₁ x₂}
  (H : to_poly e₁ = to_poly e₂) (R1 : e₁.eval X = x₁) (R2 : e₂.eval X = x₂) : x₁ = x₂ :=
by rw [ R1,  R2,  to_poly_eval, H, to_poly_eval]

meta def ring_tac (X : pexpr) : tactic unit := do
  X  to_expr X,
  `(%%x₁ = %%x₂)  target,
  r₁  reflect_expr X x₁,
  r₂  reflect_expr X x₂,
  let e₁ : expr := reflect r₁,
  let e₂ : expr := reflect r₂,
  `[refine main_thm %%X %%e₁ %%e₂ rfl _ _],
  all_goals `[simp only [ring_expr.eval,
    znum.cast_pos, znum.cast_neg, znum.cast_zero',
    pos_num.cast_bit0, pos_num.cast_bit1,
    pos_num.cast_one']]

theorem X (x : ) : (x + 1) * (x + 1) = x*x+2*x+1 :=
by do ring_tac ```(x)

#print axioms X

end ring_tac

Kevin Buzzard (Jun 12 2018 at 23:06):

Did my homework

Kevin Buzzard (Jun 12 2018 at 23:06):

I feel like an UG again

Kevin Buzzard (Jun 12 2018 at 23:08):

Notes: I had to introduce poly.smul (scalar multiplication of poly by znum) for definition of multiplication. I really tried to make simp do most of the work in general but I still had to do a lot of unfolding before I could get it going.

Kenny Lau (Jun 12 2018 at 23:08):

did you just write a tactic?

Kevin Buzzard (Jun 12 2018 at 23:08):

Not really

Kevin Buzzard (Jun 12 2018 at 23:08):

in the sense that no code I wrote started with meta

Kevin Buzzard (Jun 12 2018 at 23:09):

but check it out

Kevin Buzzard (Jun 12 2018 at 23:09):

barely any code at all has meta

Kevin Buzzard (Jun 12 2018 at 23:09):

Just reflect_expr at the very top, and ring_tac at the very bottom. Mario wrote both of those

Kevin Buzzard (Jun 12 2018 at 23:10):

Kenny here's the strat: to prove that for d : int we have (d+1)^2=d^2+2*d+1 we first prove that in a polynomial ring we have (X+1)^2=X^2+2X+1 and then deduce

Kevin Buzzard (Jun 12 2018 at 23:10):

The problem is that if Lean just sees (d+1)^2 it can't create (X+1)^2

Kevin Buzzard (Jun 12 2018 at 23:11):

so this part you have to do in meta-land

Kevin Buzzard (Jun 12 2018 at 23:11):

but it's only this part

Kevin Buzzard (Jun 12 2018 at 23:11):

unsurprisingly, this is what reflect_expr does

Kevin Buzzard (Jun 12 2018 at 23:12):

So there's a basic one-variable ring tactic with a very small amount of meta indeed, and the meta is really not hard to comprehend. I mean, it might be hard to write, but it's not at all hard to read.

Kevin Buzzard (Jun 12 2018 at 23:12):

I'll write a blog post explaining it but now it's bed time. Once again many thanks Mario.

Kevin Buzzard (Jun 12 2018 at 23:14):

Just to be clear @Kenny Lau the code I posted does seem to be a fully working baby ring

Kevin Buzzard (Jun 13 2018 at 09:55):

Actually it doesn't work in all cases, because there is currently no "canonical form" lemma. The polynomials [1] and [1,0] (=0*x+1) are different.

Kevin Buzzard (Jun 13 2018 at 09:57):

@Mario Carneiro Here are the three questions I have about this project, which basically came up when trying to write a blog post. The first two are trivial for you: what is @[derive has_reflect] and why znum rather than int?

Kevin Buzzard (Jun 13 2018 at 09:57):

The third is a bit more annoying. Because there is no algorithm to put polynomials (= lists of znums) into "canonical form", example (x : ℤ) : (x + 1) + ((-1)*x + 1) = 2 := by do ring_tac ```(x) fails

Kevin Buzzard (Jun 13 2018 at 09:58):

I think this is because the polynomials [2,0] and [2] are considered distinct.

Kevin Buzzard (Jun 13 2018 at 09:58):

Aah, I think I can fix this.

Kevin Buzzard (Jun 13 2018 at 09:58):

I think I write some "canonical_form" function

Kevin Buzzard (Jun 13 2018 at 09:58):

(not in meta land)

Kevin Buzzard (Jun 13 2018 at 09:59):

and redefine add so that it puts the polynomial into canonical form afterwards.

Kevin Buzzard (Jun 13 2018 at 09:59):

yeah yeah OK I think I can do this one.

Mario Carneiro (Jun 13 2018 at 10:36):

Why @[derive has_reflect]

This one is easy: otherwise you don't have a reflect instance for ring_expr. This function is used explicitly in ring_tac; the idea is that if A is a reflectable type then reflect (a : A) is an expr that denotes the same value as a

Mario Carneiro (Jun 13 2018 at 10:36):

so for example list has a reflect that just turns each cons into a expr.app ``list.cons a l

Mario Carneiro (Jun 13 2018 at 10:37):

and nat has a reflect that produces bit0 and bit1 expressions (which when printed appear as the number being denoted)

Mario Carneiro (Jun 13 2018 at 10:38):

But not all types have a reflect; in particular quotients and other things that make different expressions equal according to lean don't have a reflect, since you would have to open up the quotient to get the element to print

Mario Carneiro (Jun 13 2018 at 10:39):

In this case it is needed because reflect_expr produces a ring_expr, not an expr denoting a ring_expr

Mario Carneiro (Jun 13 2018 at 10:40):

I think in this case you could skip the extra step and just produce an expr directly, but that would be less structured and more meta

Mario Carneiro (Jun 13 2018 at 10:44):

Why znum

This one is more subtle, and actually I knew you would ask this question and I used it in part to prompt the question. You can view this as an efficiency move, but when there is an exponential performance gap I think it is close enough to essential to teach directly. Whenever you use proof by reflection, it is absolutely essential that you do everything you can to make the computation simple and direct, because you will be working with a fairly heavy handicap. A big no-no is using nat and int anywhere in your computation, because (as Seul has discovered) this works in unary and there is nothing you can do to prevent this when computing in the kernel

Mario Carneiro (Jun 13 2018 at 10:45):

This is in fact the raison d'etre for the num type - it allows for performing kernel computations on naturals and integers, with enough lemmas to relate them back to the more traditional nat type.

Mario Carneiro (Jun 13 2018 at 10:47):

It's a bit of a delicate move, since int and nat are in fact the more efficient ones in VM computations, so you want to convert from int to znum when storing the numbers inside the kernel data structure (ring_expr), but not before

Mario Carneiro (Jun 13 2018 at 10:51):

The third is a bit more annoying. Because there is no algorithm to put polynomials (= lists of znums) into "canonical form", example (x : ℤ) : (x + 1) + ((-1)*x + 1) = 2 := by do ring_tac ```(x) fails

I noticed this as well with your definition of poly.add. But there is actually another solution, which might be easier than applying a function that strips the high zeros after every operation. That is, define

def poly.is_eq : poly -> poly -> bool := sorry

so that trailing zeros are ignored in the equality test, and then replace to_poly e1 = to_poly e2 with poly.is_eq (to_poly e1) (to_poly e2) in the main_thm and prove it with this (weaker) assumption

Andrew Ashworth (Jun 13 2018 at 12:19):

So, if I defined a new poly type quotiented with this equality relation, I'd have to explicitly define my denotation function? That doesn't sound so bad, I think... or would the kernel get stuck trying to reduce it?

Andrew Ashworth (Jun 13 2018 at 12:19):

maybe I should just try it out with the helpful reflection template that's just been posted :)

Kevin Buzzard (Jun 13 2018 at 16:03):

def poly.is_eq_aux : list znum -> list znum -> bool
| [] [] := tt
| [] (h₂ :: t₂) := if (h₂ = 0) then poly.is_eq_aux [] t₂ else ff
| (h₁ :: t₁) [] := if (h₁ = 0) then poly.is_eq_aux t₁ [] else ff
| (h₁ :: t₁) (h₂ :: t₂) := if (h₁ = h₂) then poly.is_eq_aux t₁ t₂ else ff

def poly.is_eq : poly  poly  bool := poly.is_eq_aux

Kevin Buzzard (Jun 13 2018 at 16:04):

[recursion on poly doesn't seem to work]

Kevin Buzzard (Jun 13 2018 at 16:04):

theorem poly.eval_is_eq {α} [comm_ring α] (X : α) {p₁ p₂ : poly} :
  poly.is_eq p₁ p₂  p₁.eval X = p₂.eval X :=
begin
  revert p₂,
  induction p₁ with h₁ t₁ H₁,
  { intros p₂ H,
    induction p₂ with h₁ t₁ H₂,refl,
    unfold poly.eval,
    unfold poly.is_eq poly.is_eq_aux at H,
    split_ifs at H,swap,cases H,
    rw [h,H₂ H],
    simp,
  },
  { intros p₂ H,
    induction p₂ with h₂ t₂ H₂,
    { unfold poly.eval,
      unfold poly.is_eq poly.is_eq_aux at H,
      split_ifs at H,swap,cases H,
      rw [h,H₁ H],
      simp
    },
    { unfold poly.eval,
      unfold poly.is_eq poly.is_eq_aux at H,
      split_ifs at H,swap,cases H,
      unfold poly.is_eq at H₂,
      rw [h,H₁ H]
    }
  }

Kevin Buzzard (Jun 13 2018 at 16:04):

and then all we need is

Kevin Buzzard (Jun 13 2018 at 16:05):

theorem main_thm {α} [comm_ring α] (X : α) (e₁ e₂) {x₁ x₂}
  (H : poly.is_eq (to_poly e₁) (to_poly e₂)) (R1 : e₁.eval X = x₁) (R2 : e₂.eval X = x₂) : x₁ = x₂ :=
by rw [ R1,  R2,  to_poly_eval,poly.eval_is_eq X H, to_poly_eval]

Kevin Buzzard (Jun 13 2018 at 16:05):

example (x : ) : (x + 1) * (x + 1) = x*x+2*x+1 := by do ring_tac ```(x)

example (x : ) : (x + 1) + ((-1)*x + 1) = 2 := by do ring_tac ```(x)

Kevin Buzzard (Jun 13 2018 at 16:05):

they both work :-)

Mario Carneiro (Jun 13 2018 at 22:56):

I wouldn't recommend using a quotient, although it probably won't hurt

Mario Carneiro (Jun 13 2018 at 22:56):

The VM can erase that stuff but the kernel has to deal with it all

Mario Carneiro (Jun 13 2018 at 22:56):

if you keep all the functions simple and nondependent, the kernel doesn't have to carry around all the proof garbage

Mario Carneiro (Jun 13 2018 at 22:56):

for the reason of "working with a handicap" I mentioned

Mario Carneiro (Jun 13 2018 at 23:11):

Your definition didn't work on poly because it uses well founded recursion, which is another kernel no-no (it has to unfold the well foundedness proofs)

Mario Carneiro (Jun 13 2018 at 23:11):

Try this instead:

def poly.is_zero : poly → bool
| [] := tt
| (h :: t) := (h = 0) && poly.is_zero t

def poly.is_eq : poly → poly → bool
| l₁ [] := poly.is_zero l₁
| [] l₂ := poly.is_zero l₂
| (h₁ :: t₁) (h₂ :: t₂) := if (h₁ = h₂) then poly.is_eq t₁ t₂ else ff

(sorry for all the proof obligations!)

Mario Carneiro (Jun 13 2018 at 23:13):

(Alternatively, if you defined subtraction or an equivalent you could use only is_zero and define is_eq by (p1 - p2).is_zero)

Kevin Buzzard (Jun 14 2018 at 07:07):

Why bool and not Prop?

Moses Schönfinkel (Jun 14 2018 at 07:11):

He did it on purpose to prompt this question! ;)

Kevin Buzzard (Jun 14 2018 at 08:52):

https://xenaproject.wordpress.com/2018/06/13/ab3/

Kevin Buzzard (Jun 14 2018 at 08:53):

Comments welcome. That's how some baby version of tactic.ring works, at least. And of course many thanks to Mario, without whom that little project would have taken far longer to complete.

Kevin Buzzard (Jun 14 2018 at 08:53):

I know the post is mega-long but I am not sure I care.

Johan Commelin (Jun 14 2018 at 09:01):

This is chapter 3 of your book?

Andrew Ashworth (Jun 14 2018 at 11:23):

I read and enjoyed your blog post! My only comment is perhaps you might have a further reading section. It would be a good place to link http://adam.chlipala.net/cpdt/html/Reflection.html and https://softwarefoundations.cis.upenn.edu/vfa-current/Decide.html#lab185 for people who are trying to seriously write a reflective tactic (although, unfortunately, you have to read Coq to understand what's going on... but I think at this stage of Lean's popularity, this is somewhat necessary regardless in the tactics game). Also, minor nitpick, but znum is only uber-efficient when it is used in rfl proofs, otherwise int has special fast support

Kevin Buzzard (Jun 14 2018 at 14:31):

Many thanks Andrew. I am not a computer scientist as I'm sure you know and I don't really know about references. I have looked, briefly, at both of the things you mention, but I have never really substantially engaged with them -- I tend to stop reading when things get "too CS" because they become less relevant to what I am trying to do in Lean. Thanks for the nitpick too.

Mario Carneiro (Jun 14 2018 at 14:33):

Why bool and not Prop?

Because you can't compute with Props. You could get roughly the same behavior by using decidable p instead of bool (+ soundness proof), but since there are more dependency tricks there I suspect it's marginally slower than using bool (but not by any large margin).

Mario Carneiro (Jun 14 2018 at 14:36):

In this case, the bool definition functions both as the relation itself and its decidability proof. If you wanted to use decidable, then, it would be two definitions and a soundness proof

Kevin Buzzard (Jun 14 2018 at 17:03):

This is chapter 3 of your book?

I am not sure if this is book material.


Last updated: Dec 20 2023 at 11:08 UTC