import proveit
from proveit import used_vars, free_vars
from proveit import n, x, y
from proveit.logic import InSet
from proveit.numbers import Add, Mult, subtract
from proveit.numbers import zero, one, two, three, Integer, Natural, Real, RealPos, Complex
from proveit.numbers.rounding import Ceil, Floor, Round
%begin demonstrations
The `Round`, `Ceil`, and `Floor` classes allow us to represent standard rounding functions that take arbitrary real numbers to nearby integers in the usual way: $\text{Round}(x) = z$ represents the rounding of a real number $x$ to the nearest integer $z$; $\text{Ceil}(x) = \lceil{x}\rceil = z$ represents the rounding of a real number x to the smallest integer $z$ that is greater than or equal to $x$; and $\text{Floor}(x) = \lfloor{x}\rfloor = z$ represents the rounding of a real number x to the largest integer $z$ that is less than or equal to $x$. This ``_demonstrations_`` notebook explores the `Round`, `Ceil`, and `Floor` classes, their axioms and common theorems, and related methods.
It is straightforward to construct expressions to represent the various types of rounding operations on real numbers. Here are some basic examples of such expressions:
# representing basic rounding of a real number x
Round(x)
# representing the ceiling of a real sum
Ceil(Add(x, y))
# representing the floor of a real difference
Floor(subtract(x, y))
Let's define a simple example `Floor()` expression, $\lfloor{x-y}\rfloor$, and look at some of its attributes.
floor_x_minus_y = Floor(subtract(x,y))
floor_x_minus_y.expr_info()
We can access the Floor operand $x-y$, and identify the `Floor` operator as the outermost operation:
floor_x_minus_y.operand
floor_x_minus_y.operator
Notice in this case that the Floor's operand $x-y$ itself has operands $(x, -y)$ (because the subtract() expression is internally represented as an instance of `Add(x, -y))`:
We can also get a list of the variables and a list of the free variables:
used_vars(floor_x_minus_y)
free_vars(floor_x_minus_y)
And of course, we can reach down inside and look at the structure of the operand itself, which in this case consists of an Add() expression (even though we created it using the subtract() function):
# operator inside the operand inside the Floor
floor_x_minus_y.operand.operator
floor_x_minus_y.operand.operands
# info about the operand itself
floor_x_minus_y.operand.expr_info()
The axioms for the rounding theory establish the basic properties of the `Ceil`, `Floor`, and `Round` class functions. Some examples are shown below (and the entire complement of axioms can be found in the notebook `proveit.numbers.rounding .ipynb`).
from proveit.numbers.rounding import (ceil_is_an_int, ceil_of_x_greater_eq_x, round_up)
# When defined, Ceil is always an integer
ceil_is_an_int
# When defined, Ceil(x) is always greater than or equal to x
ceil_of_x_greater_eq_x
# The round function rounds up for reals mid-way between two integers
round_up
The theorems for the rounding theory capture many of the basic derivable properties of the `Ceil`, `Floor`, and `Round` class functions. Many of the theorems are then used for related class methods.
Below we show a few examples of such theorems (the current collection of theorems can be found in the notebook `proveit.numbers.rounding._theorems_.ipynb`):
from proveit.numbers.rounding import (
ceil_of_integer, floor_of_sum_greater_eq, floor_of_floor,
floor_plus_ceil_of_neg, round_in_terms_of_floor)
# The ceiling of an integer is just itself
ceil_of_integer
# The floor of a sum is greater than or equal to the sum of the floors
floor_of_sum_greater_eq
# The floor function is idempotent
floor_of_floor
# The floor and ceiling functions are related in a variety of ways
floor_plus_ceil_of_neg
# We can express the Round function in terms of the Floor
round_in_terms_of_floor
We consider the simplification of the rounding expression `Round((x+1)+(y+2))`, calling upon the general `Expr.simplification()` method, which itself behind the scenes eventually utilizes the `Round.do_reduced_simplification()` method and works to apply two possible simplification theorems involving integer operands of the `Round` class.
First, we define our expression:
round_xplus1_plus_yplus2 = Round(Add(Add(x,one), Add(y, two)))
We then try calling the Expression `simplification()` method. If we know nothing about the addends $x$ and $y$, we will get an error:
# If addends are not known to be integers or reals
from proveit import InstantiationFailure
try:
round_xplus1_plus_yplus2.simplification()
assert False, "Expected a ProofFailure error"
except InstantiationFailure as e:
print("EXCEPTION: the simplification theorems do not apply without more constraints. ", e)
But if we know that $x$ and $y$ are real (either because we already know that from previous work or, as we do here, we supply that information as assumptions), the simplification goes through, with Prove-It automatically simplifying $1+2$ to $3\in\mathbb{Z}$, and eventually applying a rounding theorem of the form $\forall_{x\in\mathbb{R},i\in\mathbb{Z}}\texttt{ round}(x + i) = \texttt{ round}(x) + i$:
round_xplus1_plus_yplus2.simplification(assumptions=[InSet(x, Real), InSet(y, Real)])
Interestingly, if we try to directly call the `Round.shallow_simplification()` method on our `Round(Add(Add(x+1),Add(y+2)))` expression, we get only the trivial self-equivalence back, because we now lack the `simplification()` method's pre-processing of the operand to produce $x+y+3$ first and now fail to be able to segregate the operands into a partition of reals and integers:
round_xplus1_plus_yplus2.shallow_simplification(
assumptions=[InSet(x, Real), InSet(y, Real)])
Here we consider the floor of a sum of two variables, $\lfloor \alpha + \beta \rfloor$, produced using the Expression $\texttt{Floor}(\texttt{Add}(\alpha, \beta))$, and try to simplify the expression and deduce that the $\texttt{Floor}$ expression represents an integer.
First, we define our expression:
from proveit import alpha, beta
floor_alpha_plus_beta = Floor(Add(alpha, beta))
And the `Floor.deduce_in_number_set()` method will return an error:
# If Floor operand(s) uncharacterized
from proveit import UnsatisfiedPrerequisites
try:
floor_alpha_plus_beta.deduce_in_number_set(Integer)
assert False, "Expected a ProofFailure error"
except UnsatisfiedPrerequisites as e:
print("EXCEPTION: the simplification theorems do not apply without more constraints. ", e)
But if we know or can assume some basic properties of $\alpha$ and $\beta$, we can simplify and make a deduction about the resulting form of the result. For example, suppose we know or can assume that $\alpha\in\mathbb{R}$ while $\beta\in\mathbb{Z}$. Then we can derive the equivalence $\lfloor\alpha+\beta\rfloor = \lfloor\alpha\rfloor + \beta$ (extracting the integer $\beta$ out from inside the `Floor` function):
floor_alpha_plus_beta.simplification(assumptions=[InSet(alpha, Real), InSet(beta, Integer)])
And we can deduce that the `Floor` function will produce an integer:
floor_alpha_plus_beta.deduce_in_number_set(Integer, assumptions=[InSet(alpha, Real), InSet(beta, Integer)])
One of our theorems in the rounding theory is that $\forall_{x\in\mathbb{R}}(\lfloor x \rfloor + \lceil -x \rceil = 0)$. We can use that theorem to prove the complementary theorem that $\forall_{x\in\mathbb{R}}(\lceil x \rceil + \lfloor -x \rfloor = 0)$, essentially by performing a substitution $x \mapsto -x$.
We begin by importing and displaying the original theorem:
from proveit.numbers.rounding import floor_plus_ceil_of_neg
floor_plus_ceil_of_neg
Then we instantiate that general theorem, mapping $x$ to $-x$ (and explicitly include the required assumption that $x\in\mathbb{R}$).
from proveit.numbers import Neg
floor_plus_ceil_of_neg_spec = floor_plus_ceil_of_neg.instantiate(
{x:Neg(x)}, assumptions=[InSet(x, Real)])
And generalize to our desired universal claim:
floor_plus_ceil_of_neg_spec.generalize(x, domain=Real)
The material below was developed to test various rounding-related methods. Some of this material could be integrated into the _demonstrations_
page eventually and/or deleted as development continues.
Ceil
, Floor
, and Round
Expressions¶# Some example test expressions involving Ceil
ceil_2, ceil_x_minus_y, ceil_x_minus_y_plus_2, ceil_x, ceil_y = (
Ceil(two), Ceil(subtract(x,y)), Ceil(Add(subtract(x,y), two)), Ceil(x), Ceil(y))
# Some example test expressions involving Floor
floor_2, floor_x_plus_y, floor_x_plus_y_plus_1 = (
Floor(two), Floor(Add(x, y)), Floor(Add(Add(x,y), one)))
# Some example test expressions involving Round
round_2, round_x, round_x_plus_y, round_1_plus_2, round_x_plus_2 = (
Round(two), Round(x), Round(Add(x, y)), Round(Add(one, two)), Round(Add(x, two)))
# Some more example test expressions involving Round
round_add_xy12, round_add_add, round_add_mult_mult = (
Round(Add(x, y, one, two)), Round(Add(Add(x, y),Add(one, two))),
Round(Add(Mult(x,y), Mult(one, two))))
deduce_in_number_set()
methods¶ceil_2.deduce_in_number_set(Integer)
ceil_x_minus_y_in_ints = ceil_x_minus_y.deduce_in_number_set(
Integer, assumptions=[InSet(x, Real), InSet(y, Real)])
ceil_x_minus_y_in_ints.proof().disable()
ceil_x_minus_y.deduce_in_number_set(
Integer, assumptions=[InSet(subtract(x, y), Real)])
ceil_x_minus_y.deduce_in_number_set(Natural, assumptions=[InSet(subtract(x,y), RealPos)])
# If we don't at least know the operand is real-valued,
# difficult to know what set(s) the result belongs to
try:
ceil_x_minus_y.deduce_in_number_set(Integer)
except Exception as e:
print("EXCEPTION: the closure theorems do not apply without more constraints. ", e)
floor_2.deduce_in_number_set(Natural)
floor_x_plus_y_in_ints = floor_x_plus_y.deduce_in_number_set(
Integer, assumptions=[InSet(x, Real), InSet(y, Real)])
floor_x_plus_y_in_ints.proof().disable()
floor_x_plus_y_in_ints_2 = floor_x_plus_y.deduce_in_number_set(Integer, assumptions=[InSet(Add(x,y), Real)])
floor_x_plus_y_in_ints_2.proof().disable()
floor_x_plus_y.deduce_in_number_set(Natural, assumptions=[InSet(Add(x,y), RealPos)])
# If we don't at least know the operand is real-valued,
# difficult to know what set(s) the result belongs to
try:
floor_x_plus_y.deduce_in_number_set(Integer)
except Exception as e:
print("EXCEPTION: the closure theorems do not apply without more constraints. ", e)
round_2.deduce_in_number_set(Natural)
round_x.deduce_in_number_set(Integer, assumptions=[InSet(x, Real)])
round_x.deduce_in_number_set(Natural, assumptions=[InSet(x, RealPos)])
# If we don't at least know the operand is real-valued,
# difficult to know what set(s) the result belongs to
try:
round_x.deduce_in_number_set(Integer)
except Exception as e:
print("EXCEPTION: the closure theorems do not apply without more constraints. ", e)
rounding_elimination()
method¶Some testing of the `rounding_elimination()` utility method(s), allowing the simplification of `Ceil(x)`, `Floor(x)`, and `Round(x)` to the operand $x$ when we know $x\in\mathbb{Z}$.
ceil_2
ceil_2.rounding_elimination()
ceil_x_minus_y
ceil_x_minus_y.rounding_elimination(assumptions=[InSet(x, Integer), InSet(y, Integer)])
ceil_x_minus_y_plus_2
ceil_x_minus_y_plus_2.rounding_elimination(assumptions=[InSet(x, Integer), InSet(y, Integer)])
floor_2.rounding_elimination()
floor_x_plus_y.rounding_elimination(assumptions=[InSet(x, Integer), InSet(y, Integer)])
round_2.rounding_elimination()
round_x.rounding_elimination(assumptions=[InSet(x, Integer)])
round_x_plus_y.rounding_elimination(assumptions=[InSet(x, Integer), InSet(y, Integer)])
round_1_plus_2.rounding_elimination()
round_x_plus_2.rounding_elimination(assumptions=[InSet(x, Integer)])
rounding_extraction()
method¶Some testing of the utility `rounding_extraction()` method(s), allowing the conclusion of an equivalence from `Ceil(x+i)`, `Floor(x+i)`, and `Round(x+i)` to the expression $F(x)+i$ when we know $x\in\mathbb{R}$ and $i\in\mathbb{Z}$.
ceil_x_minus_y
ceil_x_minus_y_plus_2
ceil_x_minus_y.rounding_extraction(1, assumptions=[InSet(x, Real), InSet(y, Integer)])
ceil_x_minus_y_plus_2.rounding_extraction(1, assumptions=[InSet(x, Real), InSet(y, Real)])
floor_x_plus_y
floor_x_plus_y_plus_1
floor_x_plus_y.rounding_extraction(1, assumptions=[InSet(x, Real), InSet(y, Integer)])
floor_x_plus_y_plus_1.rounding_extraction(1, assumptions=[InSet(x, Real), InSet(y, Real)])
round_add_xy12.rounding_extraction(2, assumptions=[InSet(x, Real), InSet(y, Real)])
round_add_mult_mult.rounding_extraction(1, assumptions=[InSet(x, Real), InSet(y, Real)])
round_02 = Round(Add(x, y, one, two))
round_add_add.rounding_extraction(1, assumptions=[InSet(x, Real), InSet(y, Real)])
shallow_simplification()
method¶Some testing of the `do_reduced_simplification()` method(s), allowing the simplification of `Ceil(x)`, `Floor(x)`, and `Round(x)` to the operand $x$ when we know $x\in\mathbb{Z}$, and simplification of `f(x+n)` to `f(x)+n` when $x$ is real and $n\in\mathbb{Z}$. This is not a method that would typically be called directly — instead it ends up getting called by the more general Expr.simplification() method.
# simplifying ceiling of an integer
ceil_2.shallow_simplification()
# simplifying ceiling of expression assumed to be an integer
ceil_x_minus_y.shallow_simplification(assumptions=[InSet(subtract(x, y), Integer)])
# simplifying ceiling of a sum of an assumed real and an assumed integer
ceil_x_minus_y.shallow_simplification(assumptions=[InSet(x, Real), InSet(y, Integer)])
# simplifying ceiling of sum of some assumed real numbers and an actual integer
ceil_x_minus_y_plus_2.shallow_simplification(assumptions=[InSet(x, Real), InSet(y, Real)])
# simplifying floor of an integer
floor_2.shallow_simplification()
# simplifying floor of a sum of assumed integers
floor_x_plus_y.shallow_simplification(assumptions=[InSet(x, Integer), InSet(y, Integer)])
# simplifying floor of a sum of an assumed real and an assumed integer
floor_x_plus_y.shallow_simplification(assumptions=[InSet(x, Real), InSet(y, Integer)])
# simplifying floor of a sum of some assumed real numbers and an actual integer
floor_x_plus_y_plus_1.shallow_simplification(assumptions=[InSet(x, Real), InSet(y, Real)])
round_2.shallow_simplification()
round_1_plus_2
round_1_plus_2.shallow_simplification()
round_x.shallow_simplification(assumptions=[InSet(x, Integer)])
round_x_plus_y.shallow_simplification(assumptions=[InSet(x, Real), InSet(y, Integer)])
round_x_plus_2.shallow_simplification(assumptions=[InSet(x, Real)])
round_add_add.shallow_simplification(assumptions=[InSet(Add(x,y), Real)])
# simplifying ceiling of an integer
ceil_2.simplification()
# simplifying ceiling of expression assumed to be an integer
ceil_x_minus_y.simplification(assumptions=[InSet(subtract(x, y), Integer)])
# from proveit.logic.sets import SubsetEq
# from proveit.numbers import NaturalPos
# SubsetEq(NaturalPos, Integer).proven()
# simplifying ceiling of a sum of an assumed real and an assumed integer
ceil_x_minus_y.simplification(assumptions=[InSet(x, Real), InSet(y, Integer)])
# simplifying ceiling of sum of an assumed real,
# as assumed integer, and an actual integer
ceil_x_minus_y_plus_2.simplification(assumptions=[InSet(x, Real), InSet(y, Integer)])
floor_x_plus_y_plus_1
Floor(Add(x, y, one)).inner_expr().operand.association(0, 2, assumptions=[InSet(x, Real), InSet(y, Real)], auto_simplify=False)
floor_x_plus_y_plus_1.simplification(assumptions=[InSet(x, Real), InSet(y, Real)])
Floor(Add(x, one, y))
Floor(Add(x, one, y)).simplification(assumptions=[InSet(x, Real), InSet(y, Real)])
round_add_add
round_add_add.simplification(assumptions=[InSet(x, Real), InSet(y, Real)])
round_01 = Round(Add(x, y, one, two))
round_01_alt = Round(Add(x, one, y, two))
try:
# if we don't know that the variables x and y are real,
# we cannot perform a simplification
round_01.simplification(assumptions=[InSet(x, Complex), InSet(y, Complex)])
assert False
except Exception as e:
print("Expected exception: ", e)
round_01.simplification(
assumptions=[InSet(x, Real), InSet(y, Real)])
round_01_alt.simplification(
assumptions=[InSet(x, Real), InSet(y, Real)])
from proveit.numbers import Mult
Round(Mult(x,y)).shallow_simplification(assumptions=[InSet(Mult(x, y), Integer)])
Round(Add(Mult(x,y), Add(one, two))).simplification(assumptions=[InSet(Mult(x, y), Real)])
Round(Add(one, Mult(x,y), two)).simplification(assumptions=[InSet(x, Real), InSet(y, Real)])
# If operand is not known or assumed to be an integer
# or a simple Add expression, we do not get an error but
# instead the trivial self-equivalence
Round(Mult(x,y)).shallow_simplification()
# If we try to nudge things along by assuming x, y in the set of Integer numbers,
# simplification will (by design) not automatically deduce that the
# product is an integer, so we still get the trivial self-equivalence
Round(Mult(x,y)).simplification(
assumptions=[InSet(x, Integer), InSet(y, Integer)])
# But if we assume the entire product is an integer, the simplification
# process will find the expected equivalence and extract out the
# product from the round() function
Round(Mult(x,y)).simplification(assumptions=[InSet(Mult(x, y), Integer)])
bound_via_operand_bound()
for Ceil
¶from proveit import a, b, c, x, y, z
from proveit.logic import InSet
from proveit.numbers import Integer, Real
from proveit.numbers import Less, LessEq, greater, greater_eq
ceil_assumptions = [InSet(a, Integer), InSet(b, Integer), InSet(c, Integer), InSet(x, Real), InSet(y, Real),
Less(a, x), Less(y, b), greater(y, c)]
ceil_x.bound_via_operand_bound(greater(x, a), assumptions=ceil_assumptions)
ceil_y.bound_via_operand_bound(greater(y, c), assumptions=ceil_assumptions)
%end demonstrations