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_typeensures 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
extype, thus allowing us to map expressions toex(theevalfunction 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 : exlives in the meta-world, but the normalization proofs live in the real world. Thus, we cannot directly sayps.orig = ps.prettyanywhere, 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_exponentfunction and is relatively painless since we work in areadermonad. - 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 exes to produce exprs together with a proof,
or produce the context to run ring_exp_m from an expr.