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
The atom
structure is used to represent atomic expressions:
those which ring_exp
cannot parse any further.
For instance, a + (a % b)
has a
and (a % b)
as atoms.
The ring_exp_eq
tactic does not normalize the subexpressions in atoms,
but ring_exp
does if ring_exp_eq
was not sufficient.
Atoms in fact represent equivalence classes of expressions,
modulo definitional equality.
The field index : ℕ
should be a unique number for each class,
while value : expr
contains a representative of this class.
The function resolve_atom
determines the appropriate atom
for a given expression.
We order atom
s on the order of appearance in the main expression.
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.
Each ex
stores information for its normalization proof.
The orig
expression is the expression that was passed to eval
.
The pretty
expression is the normalised form that the ex
represents.
(I didn't call this something like norm
, because there are already
too many things called norm
in mathematics!)
The field proof
contains an optional proof term of type %%orig = %%pretty
.
The value none
for the proof indicates that everything reduces to reflexivity.
(Which saves space in quite a lot of cases.)
- zero : tactic.ring_exp.ex_info → tactic.ring_exp.ex tactic.ring_exp.ex_type.sum
- sum : tactic.ring_exp.ex_info → tactic.ring_exp.ex tactic.ring_exp.ex_type.prod → tactic.ring_exp.ex tactic.ring_exp.ex_type.sum → tactic.ring_exp.ex tactic.ring_exp.ex_type.sum
- coeff : tactic.ring_exp.ex_info → tactic.ring_exp.coeff → tactic.ring_exp.ex tactic.ring_exp.ex_type.prod
- prod : tactic.ring_exp.ex_info → tactic.ring_exp.ex tactic.ring_exp.ex_type.exp → tactic.ring_exp.ex tactic.ring_exp.ex_type.prod → tactic.ring_exp.ex tactic.ring_exp.ex_type.prod
- var : tactic.ring_exp.ex_info → tactic.ring_exp.atom → tactic.ring_exp.ex tactic.ring_exp.ex_type.base
- sum_b : tactic.ring_exp.ex_info → tactic.ring_exp.ex tactic.ring_exp.ex_type.sum → tactic.ring_exp.ex tactic.ring_exp.ex_type.base
- exp : tactic.ring_exp.ex_info → tactic.ring_exp.ex tactic.ring_exp.ex_type.base → tactic.ring_exp.ex tactic.ring_exp.ex_type.prod → tactic.ring_exp.ex tactic.ring_exp.ex_type.exp
The ex
type is an abstract representation of an expression with +
, *
and ^
.
Those operators are mapped to the sum
, prod
and exp
constructors respectively.
The zero
constructor is the base case for ex sum
, e.g. 1 + 2
is represented
by (something along the lines of) sum 1 (sum 2 zero)
.
The coeff
constructor is the base case for ex prod
, and is used for numerals.
The code maintains the invariant that the coefficient is never 0
.
The var
constructor is the base case for ex exp
, and is used for atoms.
The sum_b
constructor allows for addition in the base of an exponentiation;
it serves a similar purpose as the parentheses in (a + b)^c
.
The code maintains the invariant that the argument to sum_b
is not zero
or sum _ zero
.
All of the constructors contain an ex_info
field,
used to carry around (arguments to) proof terms.
While the ex_type
parameter enforces some simplification invariants,
the following ones must be manually maintained at the risk of insufficient power:
- the argument to
coeff
must be nonzero (to ensure0 = 0 * 1
) - the argument to
sum_b
must be of the formsum a (sum b bs)
(to ensure(a + 0)^n = a^n
) - normalisation proofs of subexpressions must be
refl ps.pretty
- if we replace
sum
withcons
andzero
withnil
, the resulting list is sorted according to thelt
relation defined further down; similarly forprod
andcoeff
(to ensurea + b = b + a
).
The first two invariants could be encoded in a subtype of ex
,
but aren't (yet) to spare some implementation burden.
The other invariants cannot be encoded because we need the tactic
monad to check them.
(For example, the correct equality check of expr
is is_def_eq : expr → expr → tactic unit
.)
Return the proof information associated to the ex
.
Return the original, non-normalized version of this ex
.
Note that arguments to another ex
are always "pre-normalized":
their orig
and pretty
are equal, and their proof
is reflexivity.
Return the normalized version of this ex
.
Return the normalisation proof of the given expression.
If the proof is refl
, we give none
instead,
which helps to control the size of proof terms.
To get an actual term, use ex.proof_term
,
or use mk_proof
with the correct set of arguments.
Update the orig
and proof
fields of the ex_info
.
Intended for use in ex.set_info
.
Update the ex_info
of the given expression.
We use this to combine intermediate normalisation proofs.
Since pretty
only depends on the subexpressions,
which do not change, we do not set pretty
.
Equations
- tactic.ring_exp.coeff_has_repr = {repr := λ (x : tactic.ring_exp.coeff), repr x.value}
Convert an ex
to a string
.
Equality test for expressions.
Since equivalence of atom
s is not the same as equality,
we cannot make a true (=)
operator for ex
either.
The ordering on expressions.
As for ex.eq
, this is a linear order only in one context.
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 α.
- α : expr
- univ : level
- csr_instance : expr
- ha_instance : expr
- hm_instance : expr
- hp_instance : expr
- ring_instance : option expr
- dr_instance : option expr
- zero : expr
- one : expr
Stores the information needed in the eval
function and its dependencies,
so they can (re)construct expressions.
The eval_info
structure stores this information for one type,
and the context
combines the two types, one for bases and one for exponents.
- info_b : tactic.ring_exp.eval_info
- info_e : tactic.ring_exp.eval_info
- transp : tactic.transparency
The context
contains the full set of information needed for the eval
function.
This structure has two copies of eval_info
:
one is for the base (typically some semiring α
) and another for the exponent (always ℕ
).
When evaluating an exponent, we put info_e
in info_b
.
The ring_exp_m
monad is used instead of tactic
to store the context.
Access the instance cache.
Lift an operation in the tactic
monad to the ring_exp_m
monad.
This operation will not access the cache.
Change the context of the given computation, so that expressions are evaluated in the exponent ring, instead of the base ring.
Specialized version of mk_app
where the first two arguments are {α}
[some_class α]
.
Should be faster because it can use the cached instances.
Specialized version of mk_app
where the first two arguments are {α}
[comm_semiring α]
.
Should be faster because it can use the cached instances.
Specialized version of mk_app ``has_add.add
.
Should be faster because it can use the cached instances.
Specialized version of mk_app ``has_mul.mul
.
Should be faster because it can use the cached instances.
Specialized version of mk_app ``has_pow.pow
.
Should be faster because it can use the cached instances.
Construct a normalization proof term or return the cached one.
Construct a normalization proof term or return the cached one.
If all ex_info
have trivial proofs, return a trivial proof.
Otherwise, construct all proof terms.
Useful in applications where trivial proofs combine to another trivial proof,
most importantly to pass to mk_proof_or_refl
.
Use the proof terms as arguments to the given lemma.
If the lemma could reduce to reflexivity, consider using mk_proof_or_refl.
Use the proof terms as arguments to the given lemma. Often, we construct a proof term using congruence where reflexivity suffices. To solve this, the following function tries to get away with reflexivity.
A shortcut for adding the original terms of two expressions.
A shortcut for multiplying the original terms of two expressions.
A shortcut for exponentiating the original terms of two expressions.
Congruence lemma for constructing ex.sum
.
Congruence lemma for constructing ex.prod
.
Congruence lemma for constructing ex.exp
.
Constructs ex.zero
with the correct arguments.
Constructs ex.sum
with the correct arguments.
Constructs ex.coeff
with the correct arguments.
There are more efficient constructors for specific numerals:
if x = 0
, you should use ex_zero
; if x = 1
, use ex_one
.
Constructs ex.coeff 1
with the correct arguments.
This is a special case for optimization purposes.
Constructs ex.prod
with the correct arguments.
Constructs ex.var
with the correct arguments.
Constructs ex.sum_b
with the correct arguments.
Constructs ex.exp
with the correct arguments.
Conversion from ex base
to ex exp
.
Conversion from ex exp
to ex prod
.
A more efficient conversion from atom
to ex sum
.
The result should be the same as ex_var p >>= base_to_exp >>= exp_to_prod >>= prod_to_sum
,
except we need to calculate less intermediate steps.
Compute the sum of two coefficients.
Note that the result might not be a valid expression:
if p = -q
, then the result should be ex.zero : ex sum
instead.
The caller must detect when this happens!
The returned value is of the form ex.coeff _ (p + q)
,
with the proof of expr.of_rat p + expr.of_rat q = expr.of_rat (p + q)
.
Compute the product of two coefficients.
The returned value is of the form ex.coeff _ (p * q)
,
with the proof of expr.of_rat p * expr.of_rat q = expr.of_rat (p * q)
.
- none : tactic.ring_exp.overlap
- nonzero : tactic.ring_exp.ex tactic.ring_exp.ex_type.prod → tactic.ring_exp.overlap
- zero : tactic.ring_exp.ex tactic.ring_exp.ex_type.sum → tactic.ring_exp.overlap
Represents the way in which two products are equal except coefficient.
This type is used in the function add_overlap
.
In order to deal with equations of the form a * 2 + a = 3 * a
,
the add
function will add up overlapping products,
turning a * 2 + a
into a * 3
.
We need to distinguish a * 2 + a
from a * 2 + b
in order to do this,
and the overlap
type carries the information on how it overlaps.
The case none
corresponds to non-overlapping products, e.g. a * 2 + b
;
the case nonzero
to overlapping products adding to non-zero, e.g. a * 2 + a
(the ex prod
field will then look like a * 3
with a proof that a * 2 + a = a * 3
);
the case zero
to overlapping products adding to zero, e.g. a * 2 + a * -2
.
We distinguish those two cases because in the second, the whole product reduces to 0
.
A potential extension to the tactic would also do this for the base of exponents,
e.g. to show 2^n * 2^n = 4^n
.
Given arguments ps
, qs
of the form ps' * x
and ps' * y
respectively
return ps + qs = ps' * (x + y)
(with x
and y
arbitrary coefficients).
For other arguments, return overlap.none
.
Add two expressions.
0 + qs = 0
ps + 0 = 0
ps * x + ps * y = ps * (x + y)
(forx
,y
coefficients; usesadd_overlap
)(p + ps) + (q + qs) = p + (ps + (q + qs))
(ifp.lt q
)(p + ps) + (q + qs) = q + ((p + ps) + qs)
(if notp.lt q
)
Multiply two expressions.
x * y = (x * y)
(forx
,y
coefficients)x * (q * qs) = q * (qs * x)
(forx
coefficient)(p * ps) * y = p * (ps * y)
(fory
coefficient)(p_b^p_e * ps) * (p_b^q_e * qs) = p_b^(p_e + q_e) * (ps * qs)
(ifp_e
andq_e
are identical except coefficient)(p * ps) * (q * qs) = p * (ps * (q * qs))
(ifp.lt q
)(p * ps) * (q * qs) = q * ((p * ps) * qs)
(if notp.lt q
)
Multiply two expressions.
0 * qs = 0
(p + ps) * qs = (p * qs) + (ps * qs)
Multiply two expressions.
ps * 0 = 0
ps * (q + qs) = (ps * q) + (ps * qs)
Compute the exponentiation of two coefficients.
The returned value is of the form ex.coeff _ (p ^ q)
,
with the proof of expr.of_rat p ^ expr.of_rat q = expr.of_rat (p ^ q)
.
Exponentiate two expressions.
1 ^ qs = 1
x ^ qs = x ^ qs
(forx
coefficient)(p * ps) ^ qs = p ^ qs + ps ^ qs
Exponentiate two expressions.
ps ^ 1 = ps
0 ^ qs = 0
(note that this is handled afterps ^ 0 = 1
)(p + 0) ^ qs = p ^ qs
ps ^ (qs + 1) = ps * ps ^ qs
(note that this is handled afterp + 0 ^ qs = p ^ qs
)ps ^ qs = ps ^ qs
(otherwise)
Exponentiate two expressions.
ps ^ 0 = 1
ps ^ (q + qs) = ps ^ q * ps ^ qs
Give a simpler, more human-readable representation of the normalized expression.
Normalized expressions might have the form a^1 * 1 + 0
,
since the dummy operations reduce special cases in pattern-matching.
Humans prefer to read a
instead.
This tactic gets rid of the dummy additions, multiplications and exponentiations.
Performs a lookup of the atom a
in the list of known atoms,
or allocates a new one.
If a
is not definitionally equal to any of the list's entries,
a new atom is appended to the list and returned.
The index of this atom is kept track of in the second inductive argument.
This function is mostly useful in resolve_atom
,
which updates the state with the new list of atoms.
Convert the expression to an atom: either look up a definitionally equal atom, or allocate it as a new atom.
You probably want to use eval_base
if eval
doesn't work
instead of directly calling resolve_atom
,
since eval_base
can also handle numerals.
Treat the expression atomically: as a coefficient or atom.
Handles cases where eval
cannot treat the expression as a known operation
because it is just a number or single variable.
Negate an expression by multiplying with -1
.
Only works if there is a ring
instance; otherwise it will fail
.
Invert an expression by simplifying, applying has_inv.inv
and treating the result as an atom.
Only works if there is a division_ring
instance; otherwise it will fail
.
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
.
Compute a normalized form (of type ex
) from an expression (of type expr
).
This is the main driver of the ring_exp
tactic,
calling out to add
, mul
, pow
, etc. to parse the expr
.
Run eval
on the expression and return the result together with normalization proof.
See also eval_simple
if you want something that behaves like norm_num
.
Run eval
on the expression and simplify the result.
Returns a simplified normalized expression, together with an equality proof.
See also eval_with_proof
if you just want to check the equality of two expressions.
Compute the eval_info
for a given type α
.
Use e
to build the context for running mx
.
Repeatedly apply eval_simple
on (sub)expressions.
Tactic for solving equations of commutative (semi)rings,
allowing variables in the exponent.
This version of ring_exp
fails if the target is not an equality.
The variant ring_exp_eq!
will use a more aggressive reducibility setting
to determine equality of atoms.
Tactic for evaluating expressions in commutative (semi)rings, allowing for variables in the exponent.
This tactic extends ring
: it should solve every goal that ring
can solve.
Additionally, it knows how to evaluate expressions with complicated exponents
(where ring
only understands constant exponents).
The variants ring_exp!
and ring_exp_eq!
use a more aggessive reducibility setting to determine
equality of atoms.
For example:
Normalises expressions in commutative (semi-)rings inside of a conv
block using the tactic ring_exp
.