ring_exp
tactic #
A tactic for solving equations in commutative (semi)rings, where the exponents can also contain variables.
More precisely, expressions of the following form are supported:
- constants (non-negative integers)
- variables
- coefficients (any rational number, embedded into the (semi)ring)
- addition of expressions
- multiplication of expressions
- exponentiation of expressions (the exponent must have type
ℕ
) - subtraction and negation of expressions (if the base is a full ring)
The motivating example is proving 2 * 2^n * b = b * 2^(n+1)
,
something that the ring
tactic cannot do, but ring_exp
can.
Implementation notes #
The basic approach to prove equalities is to normalise both sides and check for equality.
The normalisation is guided by building a value in the type ex
at the meta level,
together with a proof (at the base level) that the original value is equal to
the normalised version.
The normalised version and normalisation proofs are also stored in the ex
type.
The outline of the file:
- Define an inductive family of types
ex
, parametrised overex_type
, which can represent expressions with+
,*
,^
and rational numerals. The parametrisation overex_type
ensures that associativity and distributivity are applied, by restricting which kinds of subexpressions appear as arguments to the various operators. - Represent addition, multiplication and exponentiation in the
ex
type, thus allowing us to map expressions toex
(theeval
function drives this). We apply associativity and distributivity of the operators here (helped byex_type
) and commutativity as well (by sorting the subterms; unfortunately not helped by anything). Any expression not of the above formats is treated as an atom (the same as a variable).
There are some details we glossed over which make the plan more complicated:
- The order on atoms is not initially obvious. We construct a list containing them in order of initial appearance in the expression, then use the index into the list as a key to order on.
- In the tactic, a normalized expression
ps : ex
lives in the meta-world, but the normalization proofs live in the real world. Thus, we cannot directly sayps.orig = ps.pretty
anywhere, but we have to carefully construct the proof when we computeps
. This was a major source of bugs in development! - For
pow
, the exponent must be a natural number, while the base can be any semiringα
. We swap out operations for the base ringα
with those for the exponent ringℕ
as soon as we deal with exponents. This is accomplished by thein_exponent
function and is relatively painless since we work in areader
monad. - The normalized form of an expression is the one that is useful for the tactic,
but not as nice to read. To remedy this, the user-facing normalization calls
ex.simp
.
Caveats and future work #
Subtraction cancels out identical terms, but division does not.
That is: a - a = 0 := by ring_exp
solves the goal,
but a / a := 1 by ring_exp
doesn't.
Note that 0 / 0
is generally defined to be 0
,
so division cancelling out is not true in general.
Multiplication of powers can be simplified a little bit further:
2 ^ n * 2 ^ n = 4 ^ n := by ring_exp
could be implemented
in a similar way that 2 * a + 2 * a = 4 * a := by ring_exp
already works.
This feature wasn't needed yet, so it's not implemented yet.
Tags #
ring, semiring, exponent, power
expression
section #
In this section, we define the ex
type and its basic operations.
First, we introduce the supporting types coeff
, ex_type
and ex_info
.
For understanding the code, it's easier to check out ex
itself first,
then refer back to the supporting types.
The arithmetic operations on ex
need additional definitions,
so they are defined in a later section.
- value : ℚ
Coefficients in the expression are stored in a wrapper structure,
allowing for easier modification of the data structures.
The modifications might be caching of the result of expr.of_rat
,
or using a different meta representation of numerals.
- base : tactic.ring_exp.ex_type
- sum : tactic.ring_exp.ex_type
- prod : tactic.ring_exp.ex_type
- exp : tactic.ring_exp.ex_type
The values in ex_type
are used as parameters to ex
to control the expression's structure.
Equations
- tactic.ring_exp.coeff_has_repr = {repr := λ (x : tactic.ring_exp.coeff), repr x.value}
operations
section #
This section defines the operations (on ex
) that use tactics.
They live in the ring_exp_m
monad,
which adds a cache and a list of encountered atoms to the tactic
monad.
Throughout this section, we will be constructing proof terms. The lemmas used in the construction are all defined over a commutative semiring α.
Congruence lemma for constructing ex.sum
.
Congruence lemma for constructing ex.prod
.
Congruence lemma for constructing ex.exp
.
rewrite
section #
In this section we deal with rewriting terms to fit in the basic grammar of eval
.
For example, nat.succ n
is rewritten to n + 1
before it is evaluated further.
wiring
section #
This section deals with going from expr
to ex
and back.
The main attraction is eval
, which uses add
, mul
, etc.
to calculate an ex
from a given expr
.
Other functions use ex
es to produce expr
s together with a proof,
or produce the context to run ring_exp_m
from an expr
.