use super::super::super::expr::aver_name_to_lean;
use super::super::super::tactic_ir::Tactic;
use super::super::AutoProof;
use super::super::shared::law_simp_defs;
use super::keystone::{
arith_eq, expr_var_name, inline_fn_call, keystone_pow2_fn, signed_pow2_shape, substitute_idents,
};
use crate::ast::{VerifyBlock, VerifyLaw};
use crate::codegen::CodegenContext;
#[derive(Clone, Copy, PartialEq)]
pub(super) enum RationalFloorShape {
NonnegPos,
Sign,
Bound,
}
fn rf_pow2_fn(vb: &VerifyBlock, law: &VerifyLaw, ctx: &CodegenContext) -> Option<String> {
use crate::ast::{TopLevel, VerifyKind};
use crate::ir::{FloorWindowFigure, ProofStrategy};
let cone = super::super::shared::law_simp_source_names(ctx, vb, law);
if let Some(p) = keystone_pow2_fn(vb, law, ctx) {
return Some(p);
}
for item in &ctx.items {
let TopLevel::Verify(prev) = item else {
continue;
};
let VerifyKind::Law(prev_law) = &prev.kind else {
continue;
};
if let Some(ProofStrategy::FloorDivWindow { figure }) =
super::super::law_strategy_for(ctx, &prev.fn_name, &prev_law.name)
{
let pow = match figure {
FloorWindowFigure::PowPositive { pow_fn }
| FloorWindowFigure::PowSumSplit { pow_fn }
| FloorWindowFigure::SigWindow { pow_fn, .. }
| FloorWindowFigure::ProductWindow { pow_fn, .. }
| FloorWindowFigure::FloorPow2Window { pow_fn, .. }
| FloorWindowFigure::FloorPow2Cancel { pow_fn, .. } => pow_fn,
};
let pbare = rf_bare_basename(&pow);
if cone.iter().any(|c| rf_bare_basename(c) == pbare) {
return Some(aver_name_to_lean(&pow));
}
}
}
for fd in &ctx.fn_defs {
if rf_is_pow2_rec(fd) && cone.iter().any(|c| rf_bare_basename(c) == fd.name) {
return Some(aver_name_to_lean(&fd.name));
}
}
for m in &ctx.modules {
for fd in &m.fn_defs {
if rf_is_pow2_rec(fd) && cone.iter().any(|c| rf_bare_basename(c) == fd.name) {
return Some(format!("{}.{}", m.prefix, aver_name_to_lean(&fd.name)));
}
}
}
None
}
fn rf_is_pow2_rec(fd: &crate::ast::FnDef) -> bool {
use crate::ast::{BinOp, Expr, Literal};
if fd.params.len() != 1 {
return false;
}
let Some(body) = fd.body.tail_expr() else {
return false;
};
let Expr::Match { subject, arms } = &body.node else {
return false;
};
let Expr::BinOp(BinOp::Lte, sl, sr) = &subject.node else {
return false;
};
let p = &fd.params[0].0;
if expr_var_name(&sl.node) != Some(p.as_str())
|| !matches!(&sr.node, Expr::Literal(Literal::Int(0)))
|| arms.len() != 2
{
return false;
}
let has_one = arms
.iter()
.any(|a| matches!(&a.body.node, Expr::Literal(Literal::Int(1))));
let has_rec = arms.iter().any(|a| {
let Expr::BinOp(BinOp::Mul, ml, mr) = &a.body.node else {
return false;
};
if !matches!(&ml.node, Expr::Literal(Literal::Int(2))) {
return false;
}
let Expr::FnCall(c, args) = &mr.node else {
return false;
};
let self_call = super::super::shared::expr_dotted_name(c)
.map(|n| rf_bare_basename(&n).to_string())
== Some(fd.name.clone());
let arg_ok = args.len() == 1
&& matches!(&args[0].node, Expr::BinOp(BinOp::Sub, dl, dr)
if expr_var_name(&dl.node) == Some(p.as_str())
&& matches!(&dr.node, Expr::Literal(Literal::Int(1))));
self_call && arg_ok
});
has_one && has_rec
}
pub(super) fn rational_floor_shape(
vb: &VerifyBlock,
law: &VerifyLaw,
ctx: &CodegenContext,
) -> Option<RationalFloorShape> {
use crate::ast::{BinOp, Expr, Literal};
rf_pow2_fn(vb, law, ctx)?;
let inlined = inline_fn_call(&law.lhs, ctx)?;
let is_zero = |e: &Expr| matches!(e, Expr::Literal(Literal::Int(0)));
if let Expr::FnCall(callee, args) = &inlined.node
&& rf_callee_basename(callee).as_deref() == Some("lessThan")
&& args.len() == 2
&& matches!(&args[0].node, Expr::FnCall(c, a)
if rf_callee_basename(c).as_deref() == Some("absFraction") && a.len() == 1)
&& matches!(&args[1].node, Expr::FnCall(c, a)
if rf_callee_basename(c).as_deref() == Some("pow2Signed") && a.len() == 1)
{
return Some(RationalFloorShape::Bound);
}
match &inlined.node {
Expr::BinOp(BinOp::Gte | BinOp::Gt, _l, r) if is_zero(&r.node) => {
Some(RationalFloorShape::NonnegPos)
}
Expr::BinOp(BinOp::Lte | BinOp::Lt, l, _r) if is_zero(&l.node) => {
Some(RationalFloorShape::NonnegPos)
}
Expr::Match { subject, arms }
if matches!(&subject.node, Expr::FnCall(..)) && arms.len() == 2 =>
{
Some(RationalFloorShape::Sign)
}
_ => None,
}
}
fn rf_callee_basename(callee: &crate::ast::Spanned<crate::ast::Expr>) -> Option<String> {
super::super::shared::expr_dotted_name(callee).map(|n| rf_bare_basename(&n).to_string())
}
fn rf_flatten_bool_and(
e: &crate::ast::Spanned<crate::ast::Expr>,
) -> Vec<&crate::ast::Spanned<crate::ast::Expr>> {
use crate::ast::Expr;
if let Expr::FnCall(callee, args) = &e.node
&& super::super::shared::expr_dotted_name(callee).as_deref() == Some("Bool.and")
&& args.len() == 2
{
let mut out = rf_flatten_bool_and(&args[0]);
out.extend(rf_flatten_bool_and(&args[1]));
return out;
}
vec![e]
}
fn rf_premise_proof(
citing_when: &crate::ast::Spanned<crate::ast::Expr>,
cited_when: &crate::ast::Spanned<crate::ast::Expr>,
) -> Option<String> {
let conjs = rf_flatten_bool_and(citing_when);
let n = conjs.len();
let idx = conjs
.iter()
.position(|c| arith_eq(&c.node, &cited_when.node))?;
if n == 1 {
return Some("h_when".to_string());
}
let path = if idx == 0 {
".1".repeat(n - 1)
} else if idx == n - 1 {
".2".to_string()
} else {
format!("{}.2", ".1".repeat(n - 1 - idx))
};
Some(format!(
"(by first | (simp only [Bool.and_eq_true] at h_when; exact h_when{path}) | (simp only [Bool.and_eq_true, decide_eq_true_eq] at h_when ⊢ <;> omega) | (simp only [Bool.and_eq_true] at h_when; omega))"
))
}
fn rf_starts_with_or(e: &crate::ast::Expr) -> bool {
use crate::ast::Expr;
if let Expr::FnCall(callee, args) = e {
let name = super::super::shared::expr_dotted_name(callee);
if name.as_deref() == Some("Bool.or") {
return true;
}
if name.as_deref() == Some("Bool.and")
&& let Some(first) = args.first()
{
return rf_starts_with_or(&first.node);
}
}
false
}
fn rf_resolve_fn<'a>(ctx: &'a CodegenContext, dotted: &str) -> Option<&'a crate::ast::FnDef> {
if let Some(fd) = ctx.fn_def_by_name(dotted, ctx.active_module_scope().as_deref()) {
return Some(fd);
}
let bare = dotted.rsplit('.').next().unwrap_or(dotted);
ctx.fn_defs.iter().find(|fd| fd.name == bare).or_else(|| {
ctx.modules
.iter()
.flat_map(|m| m.fn_defs.iter())
.find(|fd| fd.name == bare)
})
}
fn rf_sign_conjunct<'a>(
law: &'a VerifyLaw,
ctx: &CodegenContext,
) -> Option<&'a crate::ast::Spanned<crate::ast::Expr>> {
use crate::ast::Expr;
let when = law.when.as_ref()?;
rf_flatten_bool_and(when).into_iter().find(|c| {
let Expr::FnCall(callee, _) = &c.node else {
return false;
};
super::super::shared::expr_dotted_name(callee)
.and_then(|name| rf_resolve_fn(ctx, &name))
.and_then(|fd| fd.body.tail_expr())
.is_some_and(|body| rf_starts_with_or(&body.node))
})
}
fn rf_is_floordiv_wrapper(ctx: &CodegenContext, bare: &str) -> bool {
use crate::ast::Expr;
let Some(fd) = ctx.fn_def_by_name(bare, ctx.active_module_scope().as_deref()) else {
return false;
};
let Some(body) = fd.body.tail_expr() else {
return false;
};
let Expr::FnCall(callee, args) = &body.node else {
return false;
};
super::super::shared::expr_dotted_name(callee).as_deref() == Some("Result.withDefault")
&& args.len() == 2
}
pub(super) fn rf_bare_basename(n: &str) -> &str {
let n = n.strip_prefix("_root_.").unwrap_or(n);
n.rsplit('.').next().unwrap_or(n)
}
fn rf_filtered_defs(ctx: &CodegenContext, vb: &VerifyBlock, law: &VerifyLaw) -> Vec<String> {
let recursive: std::collections::HashSet<String> = super::super::recursive_pure_fn_names(ctx)
.iter()
.map(|n| rf_bare_basename(&aver_name_to_lean(n)).to_string())
.collect();
law_simp_defs(ctx, vb, law)
.into_iter()
.filter(|d| {
let b = rf_bare_basename(d);
!recursive.contains(b) && !rf_is_floordiv_wrapper(ctx, b)
})
.collect()
}
fn rf_record_int_fields(ctx: &CodegenContext, type_name: &str) -> Vec<String> {
use crate::ast::TypeDef;
let base = type_name.rsplit('.').next().unwrap_or(type_name).trim();
let find = |tds: &[TypeDef]| -> Option<Vec<String>> {
tds.iter().find_map(|td| match td {
TypeDef::Product { name, fields, .. } if name == base => Some(
fields
.iter()
.filter(|(_, ty)| ty.trim() == "Int")
.map(|(n, _)| n.clone())
.collect(),
),
_ => None,
})
};
if let Some(v) = find(&ctx.type_defs) {
return v;
}
for m in &ctx.modules {
if let Some(v) = find(&m.type_defs) {
return v;
}
}
Vec::new()
}
struct RfCitation {
have_name: String,
apply: String,
simp_set: String,
}
fn rf_citations(
vb: &VerifyBlock,
law: &VerifyLaw,
ctx: &CodegenContext,
intro_names: &[String],
) -> Vec<RfCitation> {
use crate::ast::{BinOp, Expr, TopLevel, VerifyKind};
let mut out = Vec::new();
let citing_when = law.when.as_ref();
for item in &ctx.items {
let TopLevel::Verify(prev) = item else {
continue;
};
if prev.line == vb.line && prev.fn_name == vb.fn_name {
break; }
let VerifyKind::Law(prev_law) = &prev.kind else {
continue;
};
if !matches!(
&prev_law.rhs.node,
Expr::Literal(crate::ast::Literal::Bool(true))
) {
continue;
}
if prev_law.givens.len() > law.givens.len() {
continue;
}
if !prev_law
.givens
.iter()
.zip(law.givens.iter())
.all(|(a, b)| a.name == b.name && a.type_name.trim() == b.type_name.trim())
{
continue;
}
let Some(inlined) = inline_fn_call(&prev_law.lhs, ctx) else {
continue;
};
let is_cmp = |e: &Expr| {
matches!(
e,
Expr::BinOp(BinOp::Lt | BinOp::Gt | BinOp::Lte | BinOp::Gte, _, _)
)
};
let is_order_content = match &inlined.node {
Expr::FnCall(callee, args)
if super::super::shared::expr_dotted_name(callee).as_deref()
== Some("Bool.and")
&& args.len() == 2 =>
{
is_cmp(&args[0].node) && is_cmp(&args[1].node)
}
other => is_cmp(other),
};
if !is_order_content {
continue;
}
let prem = match &prev_law.when {
None => String::new(),
Some(w) => {
let Some(citing) = citing_when else {
continue;
};
match rf_premise_proof(citing, w) {
Some(p) => format!(" {p}"),
None => continue,
}
}
};
let thm = format!("{}_law_{}", aver_name_to_lean(&prev.fn_name), prev_law.name);
let args = intro_names[..prev_law.givens.len()].join(" ");
let mut simp: Vec<String> = rf_filtered_defs(ctx, prev, prev_law);
simp.extend(
[
"Bool.and_eq_true",
"decide_eq_true_eq",
"ge_iff_le",
"gt_iff_lt",
]
.iter()
.map(|s| s.to_string()),
);
let have_name = format!("h_{}_{}", aver_name_to_lean(&prev.fn_name), prev_law.name);
out.push(RfCitation {
apply: format!("{thm} {args}{prem}"),
simp_set: simp.join(", "),
have_name,
});
}
out
}
fn rf_emit(ctx: &CodegenContext, e: &crate::ast::Spanned<crate::ast::Expr>) -> String {
super::super::super::expr::emit_expr_legacy(e, ctx, None)
}
fn rf_inline_fn_call(
call: &crate::ast::Spanned<crate::ast::Expr>,
ctx: &CodegenContext,
) -> Option<crate::ast::Spanned<crate::ast::Expr>> {
use crate::ast::Expr;
let Expr::FnCall(callee, args) = &call.node else {
return None;
};
let name = super::super::shared::expr_dotted_name(callee)?;
let fd = rf_resolve_fn(ctx, &name)?;
if fd.params.len() != args.len() {
return None;
}
let body = fd.body.tail_expr()?;
let mut map: std::collections::HashMap<String, crate::ast::Spanned<crate::ast::Expr>> =
std::collections::HashMap::new();
for ((pname, _), arg) in fd.params.iter().zip(args.iter()) {
map.insert(pname.clone(), arg.clone());
}
Some(substitute_idents(body, &map))
}
struct RfFpFields {
sign: String,
sig: String,
exp: String,
width: String,
}
fn rf_fpvalue_fields(
frac: &crate::ast::Spanned<crate::ast::Expr>,
ctx: &CodegenContext,
) -> Option<RfFpFields> {
use crate::ast::{BinOp, Expr};
let Expr::RecordCreate { fields, .. } = &frac.node else {
return None;
};
let top = fields.iter().find(|(n, _)| n == "top").map(|(_, e)| e)?;
let bottom = fields.iter().find(|(n, _)| n == "bottom").map(|(_, e)| e)?;
let Expr::FnCall(_, bargs) = &bottom.node else {
return None;
};
let Expr::BinOp(BinOp::Sub, wbase, _) = &bargs.first()?.node else {
return None;
};
let Expr::BinOp(BinOp::Mul, l, r) = &top.node else {
return None;
};
let Expr::BinOp(BinOp::Mul, sign_b, sig_b) = &l.node else {
return None;
};
let Expr::FnCall(_, eargs) = &r.node else {
return None;
};
let exp_b = eargs.first()?;
Some(RfFpFields {
sign: rf_emit(ctx, sign_b),
sig: rf_emit(ctx, sig_b),
exp: rf_emit(ctx, exp_b),
width: rf_emit(ctx, wbase),
})
}
fn rf_window_law(
vb: &VerifyBlock,
law: &VerifyLaw,
ctx: &CodegenContext,
) -> Option<(String, String)> {
use crate::ast::{BinOp, Expr, TopLevel, VerifyKind};
let is_cmp = |e: &Expr| {
matches!(
e,
Expr::BinOp(BinOp::Lt | BinOp::Gt | BinOp::Lte | BinOp::Gte, _, _)
)
};
for item in &ctx.items {
let TopLevel::Verify(prev) = item else {
continue;
};
if prev.line == vb.line && prev.fn_name == vb.fn_name {
break; }
let VerifyKind::Law(prev_law) = &prev.kind else {
continue;
};
if !matches!(
&prev_law.rhs.node,
Expr::Literal(crate::ast::Literal::Bool(true))
) {
continue;
}
if prev_law.givens.len() > law.givens.len()
|| !prev_law
.givens
.iter()
.zip(law.givens.iter())
.all(|(a, b)| a.name == b.name && a.type_name.trim() == b.type_name.trim())
{
continue;
}
let Some(inlined) = inline_fn_call(&prev_law.lhs, ctx) else {
continue;
};
let Expr::FnCall(callee, args) = &inlined.node else {
continue;
};
if super::super::shared::expr_dotted_name(callee).as_deref() != Some("Bool.and")
|| args.len() != 2
{
continue;
}
if !is_cmp(&args[0].node) || !is_cmp(&args[1].node) {
continue;
}
let thm = format!("{}_law_{}", aver_name_to_lean(&prev.fn_name), prev_law.name);
let unfold = format!("_root_.{}", aver_name_to_lean(&prev.fn_name));
return Some((thm, unfold));
}
None
}
fn rf_homomorphism_name(ctx: &CodegenContext, pow_lean: &str) -> Option<String> {
use crate::ast::{BinOp, Expr, TopLevel, VerifyKind};
let pow_base = rf_bare_basename(pow_lean);
let is_hom = |lw: &VerifyLaw| -> bool {
if let Expr::BinOp(BinOp::Mul, a, b) = &lw.rhs.node {
let is_pow_call = |e: &Expr| {
matches!(e, Expr::FnCall(c, _)
if super::super::shared::expr_dotted_name(c)
.as_deref()
.map(rf_bare_basename) == Some(pow_base))
};
return is_pow_call(&a.node) && is_pow_call(&b.node);
}
false
};
for item in &ctx.items {
if let TopLevel::Verify(prev) = item
&& let VerifyKind::Law(prev_law) = &prev.kind
&& rf_bare_basename(&prev.fn_name) == pow_base
&& is_hom(prev_law)
{
return Some(format!(
"{}_law_{}",
aver_name_to_lean(&prev.fn_name),
prev_law.name
));
}
}
for m in &ctx.modules {
for prev in &m.verify_laws {
if let VerifyKind::Law(prev_law) = &prev.kind
&& rf_bare_basename(&prev.fn_name) == pow_base
&& is_hom(prev_law)
{
return Some(format!(
"{}.{}_law_{}",
m.prefix,
aver_name_to_lean(&prev.fn_name),
prev_law.name
));
}
}
}
None
}
struct RfGeneralFields {
sgn_fn: String,
exp: String,
sign: String,
sig: String,
width: String,
}
fn rf_general_value_fields(
val: &crate::ast::Spanned<crate::ast::Expr>,
ctx: &CodegenContext,
int_pow: &str,
) -> Option<RfGeneralFields> {
use crate::ast::{BinOp, Expr};
let Expr::FnCall(tcallee, targs) = &val.node else {
return None;
};
if super::super::shared::expr_dotted_name(tcallee)?
.rsplit('.')
.next()
!= Some("times")
|| targs.len() != 2
{
return None;
}
let Expr::FnCall(acallee, aargs) = &targs[0].node else {
return None;
};
if aargs.len() != 1 {
return None;
}
let sgn_dotted = super::super::shared::expr_dotted_name(acallee)?;
let sgn_fd = rf_resolve_fn(ctx, &sgn_dotted)?;
if !signed_pow2_shape(sgn_fd, int_pow) {
return None;
}
let Expr::RecordCreate { fields, .. } = &targs[1].node else {
return None;
};
let top = fields.iter().find(|(n, _)| n == "top").map(|(_, e)| e)?;
let bottom = fields.iter().find(|(n, _)| n == "bottom").map(|(_, e)| e)?;
let Expr::BinOp(BinOp::Mul, sign_b, sig_b) = &top.node else {
return None;
};
let Expr::FnCall(_, bargs) = &bottom.node else {
return None;
};
let Expr::BinOp(BinOp::Sub, wbase, _) = &bargs.first()?.node else {
return None;
};
Some(RfGeneralFields {
sgn_fn: aver_name_to_lean(&sgn_dotted),
exp: rf_emit(ctx, &aargs[0]),
sign: rf_emit(ctx, sign_b),
sig: rf_emit(ctx, sig_b),
width: rf_emit(ctx, wbase),
})
}
struct RfLeaf {
sig: crate::ast::Spanned<crate::ast::Expr>,
exp: crate::ast::Spanned<crate::ast::Expr>,
width: crate::ast::Spanned<crate::ast::Expr>,
}
fn rf_expand_sig(
sig: &crate::ast::Spanned<crate::ast::Expr>,
ctx: &CodegenContext,
) -> Vec<crate::ast::Spanned<crate::ast::Expr>> {
use crate::ast::Expr;
if let Expr::FnCall(c, _) = &sig.node
&& let Some(name) = super::super::shared::expr_dotted_name(c)
{
let base = rf_bare_basename(&name);
let recursive: std::collections::HashSet<String> =
super::super::recursive_pure_fn_names(ctx)
.iter()
.map(|n| rf_bare_basename(&aver_name_to_lean(n)).to_string())
.collect();
if !recursive.contains(base)
&& !rf_is_floordiv_wrapper(ctx, base)
&& let Some(inl) = rf_inline_fn_call(sig, ctx)
&& let Expr::Match { arms, .. } = &inl.node
{
return arms
.iter()
.flat_map(|a| rf_expand_sig(&a.body, ctx))
.collect();
}
}
vec![sig.clone()]
}
fn rf_inline_wrappers(
e: &crate::ast::Spanned<crate::ast::Expr>,
ctx: &CodegenContext,
) -> crate::ast::Spanned<crate::ast::Expr> {
let recursive: std::collections::HashSet<String> = super::super::recursive_pure_fn_names(ctx)
.iter()
.map(|n| rf_bare_basename(&aver_name_to_lean(n)).to_string())
.collect();
rf_inline_wrappers_inner(e, ctx, &recursive)
}
fn rf_inline_wrappers_inner(
e: &crate::ast::Spanned<crate::ast::Expr>,
ctx: &CodegenContext,
recursive: &std::collections::HashSet<String>,
) -> crate::ast::Spanned<crate::ast::Expr> {
use crate::ast::{Expr, Spanned};
let rec = |x: &crate::ast::Spanned<Expr>| rf_inline_wrappers_inner(x, ctx, recursive);
let mapped = match &e.node {
Expr::BinOp(op, a, b) => {
Spanned::bare(Expr::BinOp(*op, Box::new(rec(a)), Box::new(rec(b))))
}
Expr::Neg(a) => Spanned::bare(Expr::Neg(Box::new(rec(a)))),
Expr::FnCall(c, args) => {
Spanned::bare(Expr::FnCall(c.clone(), args.iter().map(rec).collect()))
}
_ => e.clone(),
};
if let Expr::FnCall(c, _) = &mapped.node
&& let Some(name) = super::super::shared::expr_dotted_name(c)
{
let base = rf_bare_basename(&aver_name_to_lean(&name)).to_string();
if !recursive.contains(&base)
&& !rf_is_floordiv_wrapper(ctx, &base)
&& let Some(inl) = rf_inline_fn_call(&mapped, ctx)
{
return rf_inline_wrappers_inner(&inl, ctx, recursive);
}
}
mapped
}
struct RfFloorInfo {
m: String,
u: String,
eexp: String,
c: u8,
}
fn rf_leaf_floor(
sig: &crate::ast::Spanned<crate::ast::Expr>,
ctx: &CodegenContext,
) -> Option<RfFloorInfo> {
use crate::ast::{BinOp, Expr, Literal};
let is_one = |e: &Expr| matches!(e, Expr::Literal(Literal::Int(1)));
let is_two = |e: &Expr| matches!(e, Expr::Literal(Literal::Int(2)));
let is_floordiv = |e: &crate::ast::Spanned<Expr>| -> bool {
matches!(&e.node, Expr::FnCall(c, _)
if super::super::shared::expr_dotted_name(c)
.map(|n| rf_is_floordiv_wrapper(ctx, rf_bare_basename(&aver_name_to_lean(&n))))
== Some(true))
};
let inner = match &sig.node {
Expr::BinOp(BinOp::Add, a, b) if is_one(&b.node) => a.as_ref(),
_ => sig,
};
let (fd, c): (&crate::ast::Spanned<Expr>, u8) = match &inner.node {
Expr::BinOp(BinOp::Mul, a, b) if is_two(&a.node) && is_floordiv(b) => (b.as_ref(), 2),
Expr::BinOp(BinOp::Mul, a, b) if is_two(&b.node) && is_floordiv(a) => (a.as_ref(), 2),
_ if is_floordiv(inner) => (inner, 1),
_ => return None,
};
let Expr::FnCall(_, fargs) = &fd.node else {
return None;
};
let numer = fargs.first()?;
let Expr::BinOp(BinOp::Mul, _s, powcall) = &numer.node else {
return None;
};
let Expr::FnCall(_, pargs) = &powcall.node else {
return None;
};
let eexp = rf_emit(ctx, pargs.first()?);
Some(RfFloorInfo {
m: rf_emit(ctx, fd),
u: rf_emit(ctx, numer),
eexp,
c,
})
}
fn rf_collect_leaves(
expr: &crate::ast::Spanned<crate::ast::Expr>,
ctx: &CodegenContext,
out: &mut Vec<RfLeaf>,
) -> Option<()> {
use crate::ast::Expr;
match &expr.node {
Expr::Match { arms, .. } => {
for a in arms {
rf_collect_leaves(&a.body, ctx, out)?;
}
Some(())
}
Expr::RecordCreate { fields, .. } => {
let sig = fields
.iter()
.find(|(n, _)| n == "sigBits")
.map(|(_, e)| e)?;
let exp = fields.iter().find(|(n, _)| n == "exp").map(|(_, e)| e)?;
let width = fields.iter().find(|(n, _)| n == "width").map(|(_, e)| e)?;
for s in rf_expand_sig(sig, ctx) {
out.push(RfLeaf {
sig: s,
exp: exp.clone(),
width: width.clone(),
});
}
Some(())
}
_ => None,
}
}
fn emit_rational_floor_bound_matched(
vb: &VerifyBlock,
law: &VerifyLaw,
ctx: &CodegenContext,
intro_names: &[String],
) -> Option<AutoProof> {
use crate::ast::Expr;
let pow = rf_pow2_fn(vb, law, ctx)?;
let inlined = inline_fn_call(&law.lhs, ctx)?;
let Expr::FnCall(_lt, lt_args) = &inlined.node else {
return None;
};
if lt_args.len() != 2 {
return None;
}
let Expr::FnCall(_abs, abs_args) = <_args[0].node else {
return None;
};
let err_call = abs_args.first()?;
let Expr::FnCall(_ps, ps_args) = <_args[1].node else {
return None;
};
let le_expr = ps_args.first()?;
let Expr::FnCall(_te, te_args) = &err_call.node else {
return None;
};
if te_args.len() != 2 {
return None;
}
let f_name = expr_var_name(&te_args[0].node)?.to_string();
let i_name = expr_var_name(&te_args[1].node)?.to_string();
let minus_inlined = rf_inline_fn_call(err_call, ctx)?;
let Expr::FnCall(_m, m_args) = &minus_inlined.node else {
return None;
};
if m_args.len() != 2 {
return None;
}
let arg_round_call = |e: &crate::ast::Spanned<Expr>| -> Option<crate::ast::Spanned<Expr>> {
let Expr::FnCall(_, a) = &e.node else {
return None;
};
let inner = a.first()?;
matches!(&inner.node, Expr::FnCall(..)).then(|| inner.clone())
};
let (orig_value, rounded_call) = match (arg_round_call(&m_args[0]), arg_round_call(&m_args[1]))
{
(Some(a0), Some(a1)) => {
let two = |c: &crate::ast::Spanned<Expr>| matches!(&c.node, Expr::FnCall(_, ar) if ar.len() == 2);
if two(&a1) {
(m_args[0].clone(), a1)
} else {
(m_args[1].clone(), a0)
}
}
(Some(a0), None) => (m_args[1].clone(), a0),
(None, Some(a1)) => (m_args[0].clone(), a1),
(None, None) => return None,
};
let fpv_f = rf_inline_fn_call(&orig_value, ctx)?;
let gf = rf_general_value_fields(&fpv_f, ctx, &pow)?;
let value_base = match &orig_value.node {
Expr::FnCall(c, _) => {
rf_bare_basename(&super::super::shared::expr_dotted_name(c)?).to_string()
}
_ => return None,
};
let rounded_rec = rf_inline_fn_call(&rounded_call, ctx)?;
if !matches!(&rounded_rec.node, Expr::Match { .. }) {
return None;
}
let mut leaves: Vec<RfLeaf> = Vec::new();
rf_collect_leaves(&rounded_rec, ctx, &mut leaves)?;
if leaves.is_empty() {
return None;
}
let (window_thm, window_fn) = rf_window_law(vb, law, ctx)?;
let hom = rf_homomorphism_name(ctx, &pow)?;
let le = rf_emit(ctx, le_expr);
let sgnfn = gf.sgn_fn.clone();
let exp = gf.exp.clone();
let sign = gf.sign.clone();
let sig = gf.sig.clone();
let pet = format!("({sgnfn} ({exp})).top");
let peb = format!("({sgnfn} ({exp})).bottom");
let plt = format!("({sgnfn} ({le})).top");
let plb = format!("({sgnfn} ({le})).bottom");
let pe1t = format!("({sgnfn} ({exp} + 1)).top");
let pe1b = format!("({sgnfn} ({exp} + 1)).bottom");
let pw = format!("{pow} ({} - 1)", gf.width);
let pp = format!("{pow} ({i_name} - 1)");
let base = format!("{}_law_{}__rfb", aver_name_to_lean(&vb.fn_name), law.name);
let primary = leaves
.iter()
.find_map(|l| rf_leaf_floor(&rf_inline_wrappers(&l.sig, ctx), ctx));
let mut alts: Vec<String> = Vec::new();
let mut seen: std::collections::HashSet<(String, u8)> = std::collections::HashSet::new();
let mut has_carry = false;
let mut needs_degenerate = false;
let mut win_exps: Vec<String> = Vec::new();
for l in &leaves {
let goal_sig = rf_inline_wrappers(&l.sig, ctx);
let q = rf_emit(ctx, &goal_sig);
let le_exp = rf_emit(ctx, &l.exp);
let offset: u8 = if le_exp == exp {
0
} else if matches!(&l.exp.node, Expr::BinOp(crate::ast::BinOp::Add, a, b)
if rf_emit(ctx, a) == exp && matches!(&b.node, Expr::Literal(crate::ast::Literal::Int(1))))
{
1
} else {
return None;
};
if !seen.insert((q.clone(), offset)) {
continue;
}
let (av, ab, k2) = if offset == 0 {
(pet.clone(), peb.clone(), "1")
} else {
has_carry = true;
(pe1t.clone(), pe1b.clone(), "2")
};
let width_leaf = rf_emit(ctx, &l.width);
let p_leaf = format!("{pow} ({width_leaf} - 1)");
let (m, u, c, ehalf) = match rf_leaf_floor(&goal_sig, ctx) {
Some(fi) => {
if !win_exps.contains(&fi.eexp) {
win_exps.push(fi.eexp.clone());
}
let e = fi.eexp.clone();
(fi.m, fi.u, fi.c, Some(e))
}
None if offset == 1 => {
let pr = primary.as_ref()?;
if !win_exps.contains(&pr.eexp) {
win_exps.push(pr.eexp.clone());
}
(pr.m.clone(), pr.u.clone(), pr.c, Some(pr.eexp.clone()))
}
None => {
needs_degenerate = true;
(String::from("1"), sig.clone(), 1u8, None)
}
};
let half = match &ehalf {
Some(e) => {
format!(" | (have hps := {base}__pow_succ_p ({e}) (by omega); grind)")
}
None => String::new(),
};
alts.push(format!(
" | (apply {base}__away_leaf ({pet}) ({peb}) ({plt}) ({plb}) ({av}) ({ab}) ({pw}) ({p_leaf}) ({u}) {c} {k2} ({q}) ({sig}) ({sign}) ({m}) <;> first | assumption | omega | grind{half})"
));
}
let when = law.when.as_ref()?;
let conjs = rf_flatten_bool_and(when);
let sign_conj = rf_sign_conjunct(law, ctx)?;
let fmt_idx = conjs
.iter()
.position(|c| arith_eq(&c.node, &sign_conj.node))?;
let isfp_lean = match &sign_conj.node {
Expr::FnCall(callee, _) => {
aver_name_to_lean(&super::super::shared::expr_dotted_name(callee)?)
}
_ => return None,
};
let ncj = conjs.len();
let mut pat = "h_rfbp0".to_string();
for k in 1..ncj {
pat = format!("⟨{pat}, h_rfbp{k}⟩");
}
let fmtname = format!("h_rfbp{fmt_idx}");
let mut support_lines: Vec<String> =
super::super::floor_window::pow2_signed_pos_support(&base, &pow, &sgnfn)
.lines()
.map(|l| l.to_string())
.collect();
support_lines.extend(
super::super::floor_window::matched_leaf_support(&base, &pow)
.lines()
.map(|l| l.to_string()),
);
let rational_prims = [
"absFraction",
"absInt",
"lessThan",
"minus",
"times",
"plus",
"sameValue",
"negate",
];
let sgn_base = rf_bare_basename(&sgnfn).to_string();
let filtered = rf_filtered_defs(ctx, vb, law)
.into_iter()
.filter(|d| rf_bare_basename(d) != sgn_base)
.collect::<Vec<_>>();
let is_set2 = |d: &str| {
let b = rf_bare_basename(d);
b == value_base || rational_prims.contains(&b)
};
let set1 = filtered
.iter()
.filter(|d| !is_set2(d))
.cloned()
.collect::<Vec<_>>()
.join(", ");
let set2 = filtered
.iter()
.filter(|d| is_set2(d))
.cloned()
.collect::<Vec<_>>()
.join(", ");
let intro = format!(" intro {} h_when", intro_names.join(" "));
let mut body: Vec<String> = vec![intro, " first".to_string(), " | (".to_string()];
body.push(" simp only [Bool.and_eq_true, decide_eq_true_eq] at h_when".to_string());
body.push(format!(" obtain {pat} := h_when"));
body.push(format!(" have hpp : 0 < {pp} := {base}__pow_pos _"));
body.push(format!(" have hpw : 0 < {pw} := {base}__pow_pos _"));
body.push(format!(" have hPSeT : 0 < {pet} := {base}__sgnt_pos _"));
body.push(format!(" have hPSeB : 0 < {peb} := {base}__sgnb_pos _"));
body.push(format!(" have hPSlT : 0 < {plt} := {base}__sgnt_pos _"));
body.push(format!(" have hPSlB : 0 < {plb} := {base}__sgnb_pos _"));
if has_carry {
body.push(format!(
" have hPSe1T : 0 < {pe1t} := {base}__sgnt_pos _"
));
body.push(format!(
" have hPSe1B : 0 < {pe1b} := {base}__sgnb_pos _"
));
}
body.push(format!(" have hsign : {sign} = 1 ∨ {sign} = -1 := by unfold {isfp_lean} at {fmtname}; simp only [Bool.and_eq_true, Bool.or_eq_true, beq_iff_eq, decide_eq_true_eq] at {fmtname}; exact {fmtname}.1"));
body.push(format!(
" have hpowi : {pow} {i_name} = 2 * {pp} := {base}__pow_of_pos {i_name} (by omega)"
));
if has_carry {
body.push(format!(" have hp1 : {pow} 1 = 2 := by have hx := {base}__pow_of_pos 1 (by omega); rw [show (1:Int) - 1 = 0 by omega, {base}__pow_of_nonpos 0 (by omega)] at hx; omega"));
}
for (wi, ee) in win_exps.iter().enumerate() {
body.push(format!(
" have h_win{wi} := {window_thm} {f_name} (({ee}) + 1)"
));
body.push(format!(
" simp only [{window_fn}, Bool.and_eq_true, decide_eq_true_eq] at h_win{wi}"
));
body.push(format!(
" rw [show (({ee}) + 1) - 1 = ({ee}) by omega] at h_win{wi}"
));
body.push(format!(" obtain ⟨hwlo{wi}, hwhi{wi}⟩ := h_win{wi}"));
}
if needs_degenerate {
body.push(format!(" have hnorm := {fmtname}"));
body.push(format!(" unfold {isfp_lean} at hnorm"));
body.push(" simp only [Bool.and_eq_true, Bool.or_eq_true, beq_iff_eq, decide_eq_true_eq] at hnorm".to_string());
body.push(format!(
" have hp0 : {pow} (1 - 1) = 1 := {base}__pow_of_nonpos (1 - 1) (by omega)"
));
body.push(format!(" have hwgt : ¬ ({} <= 0) := by", gf.width));
body.push(" intro hle".to_string());
body.push(" have hn1 := hnorm.2.1".to_string());
body.push(" have hn2 := hnorm.2.2".to_string());
body.push(format!(
" rw [{base}__pow_of_nonpos ({} - 1) (by omega)] at hn1",
gf.width
));
body.push(format!(
" rw [{base}__pow_of_nonpos {} (by omega)] at hn2",
gf.width
));
body.push(" omega".to_string());
body.push(format!(
" have hwsucc : {pow} {} = 2 * {pow} ({} - 1) := {base}__pow_of_pos {} hwgt",
gf.width, gf.width, gf.width
));
body.push(format!(
" have hwlo_deg : {pow} ({} - 1) * 1 <= {sig} := by have := hnorm.2.1; omega",
gf.width
));
body.push(format!(
" have hwhi_deg : {sig} < {pow} ({} - 1) * (1 + 1) := by",
gf.width
));
body.push(format!(
" rw [show {pow} ({} - 1) * (1 + 1) = 2 * {pow} ({} - 1) by omega, ← hwsucc]",
gf.width, gf.width
));
body.push(" exact hnorm.2.2".to_string());
}
body.push(format!(
" have hlink : {pet} * {plb} = {plt} * {pp} * {peb} := by"
));
body.push(format!(" by_cases hE : {exp} < 0"));
body.push(format!(" · have hLE : {le} < 0 := by omega"));
body.push(format!(" have hh := {hom} ({i_name} - 1) (0 - {exp}) (by simp only [Bool.and_eq_true, ge_iff_le, decide_eq_true_eq]; omega)"));
body.push(format!(
" rw [show ({i_name} - 1) + (0 - {exp}) = 0 - ({le}) by omega] at hh"
));
body.push(format!(
" unfold {sgnfn}; rw [if_pos hE, if_pos hLE]; grind"
));
body.push(format!(" · by_cases hLE : {le} < 0"));
body.push(format!(" · have hh := {hom} {exp} (0 - ({le})) (by simp only [Bool.and_eq_true, ge_iff_le, decide_eq_true_eq]; omega)"));
body.push(format!(
" rw [show {exp} + (0 - ({le})) = {i_name} - 1 by omega] at hh"
));
body.push(format!(
" unfold {sgnfn}; rw [if_neg hE, if_pos hLE]; grind"
));
body.push(format!(" · have hh := {hom} ({le}) ({i_name} - 1) (by simp only [Bool.and_eq_true, ge_iff_le, decide_eq_true_eq]; omega)"));
body.push(format!(
" rw [show ({le}) + ({i_name} - 1) = {exp} by omega] at hh"
));
body.push(format!(
" unfold {sgnfn}; rw [if_neg hE, if_neg hLE]; grind"
));
if has_carry {
body.push(format!(
" have hdbl : {pe1t} * {peb} = 2 * {pet} * {pe1b} := by"
));
body.push(format!(" by_cases hE : ({exp} + 1) < 0"));
body.push(format!(" · have hLE : {exp} < 0 := by omega"));
body.push(format!(" have hh := {hom} 1 (0 - ({exp} + 1)) (by simp only [Bool.and_eq_true, ge_iff_le, decide_eq_true_eq]; omega)"));
body.push(format!(
" rw [show (1 : Int) + (0 - ({exp} + 1)) = 0 - {exp} by omega, hp1] at hh"
));
body.push(format!(
" unfold {sgnfn}; rw [if_pos hE, if_pos hLE]; grind"
));
body.push(format!(" · by_cases hLE : {exp} < 0"));
body.push(format!(" · have hh := {hom} ({exp} + 1) (0 - {exp}) (by simp only [Bool.and_eq_true, ge_iff_le, decide_eq_true_eq]; omega)"));
body.push(format!(
" rw [show ({exp} + 1) + (0 - {exp}) = 1 by omega, hp1] at hh"
));
body.push(format!(
" unfold {sgnfn}; rw [if_neg hE, if_pos hLE]; grind"
));
body.push(format!(" · have hh := {hom} {exp} 1 (by simp only [Bool.and_eq_true, ge_iff_le, decide_eq_true_eq]; omega)"));
body.push(" rw [hp1] at hh".to_string());
body.push(format!(
" unfold {sgnfn}; rw [if_neg hE, if_neg hLE]; grind"
));
}
body.push(format!(" simp only [{set1}]"));
body.push(" repeat' split".to_string());
body.push(format!(
" all_goals (simp only [{set2}, decide_eq_true_eq])"
));
body.push(" all_goals (try simp only [beq_iff_eq] at *)".to_string());
body.push(" all_goals (".to_string());
body.push(" first".to_string());
for a in &alts {
body.push(a.clone());
}
body.push(" )".to_string());
body.push(" )".to_string());
let floor = if super::super::super::tactic_ir::speculative::probing() {
let id = format!("{}.{}", vb.fn_name, law.name);
super::super::super::tactic_ir::speculative::record_probed(&id);
format!(" | (trace \"AVERSPEC_SORRY:{id}\"; sorry)")
} else {
" | sorry".to_string()
};
body.push(floor);
Some(AutoProof {
support_lines,
body: Tactic::raw(body),
replaces_theorem: false,
})
}
fn render_frac_bound_support(base: &str, abs: &str, floor: &str) -> String {
format!(
r#"theorem {base}__floordiv_eq (a d : Int) (hd : 0 < d) : {floor} a d = a / d := by
have hne : ¬((d == 0) = true) := by simp only [beq_iff_eq]; omega
simp only [{floor}]
rw [if_neg hne]
simp only [Except.withDefault]
theorem {base}__floor_window (a d : Int) (hd : 0 < d) :
d * ({floor} a d) ≤ a ∧ a < d * ({floor} a d + 1) := by
rw [{base}__floordiv_eq a d hd]
have hd0 : d ≠ 0 := by omega
have heq := Int.ediv_add_emod a d
have h0 := Int.emod_nonneg a hd0
have h1 := Int.emod_lt_of_pos a hd
have hexp : d * (a / d + 1) = d * (a / d) + d := by rw [Int.mul_add, Int.mul_one]
refine ⟨by omega, ?_⟩
rw [hexp]; omega
theorem {base}__abs_trunc_mag (a b Ut Ub q : Int)
(ha0 : a ≠ 0) (hb0 : b ≠ 0)
(hwlo : {abs} b * Ut * q ≤ {abs} a * Ub) :
{abs} (a * Ub - (if a * b ≥ 0 then (1:Int) else 0 - 1) * q * Ut * b)
= {abs} a * Ub - {abs} b * Ut * q := by
have hR : 0 ≤ {abs} a * Ub - {abs} b * Ut * q := by omega
have hcases :
a * Ub - (if a * b ≥ 0 then (1:Int) else 0 - 1) * q * Ut * b
= ({abs} a * Ub - {abs} b * Ut * q)
∨ a * Ub - (if a * b ≥ 0 then (1:Int) else 0 - 1) * q * Ut * b
= -({abs} a * Ub - {abs} b * Ut * q) := by
unfold {abs}
rcases Int.lt_or_le a 0 with ha | ha <;> rcases Int.lt_or_le b 0 with hb | hb
· have hab : a * b ≥ 0 := by
have h := Int.mul_pos (show (0:Int) < -a by omega) (show (0:Int) < -b by omega)
rw [Int.neg_mul, Int.mul_neg, Int.neg_neg] at h; omega
rw [if_pos hab, if_pos ha, if_pos hb]; right; grind
· have hbp : 0 < b := by omega
have hab : ¬ (a * b ≥ 0) := by
have h := Int.mul_pos (show (0:Int) < -a by omega) hbp
rw [Int.neg_mul] at h; omega
rw [if_neg hab, if_pos ha, if_neg (by omega)]; right; grind
· have hap : 0 < a := by omega
have hab : ¬ (a * b ≥ 0) := by
have h := Int.mul_pos hap (show (0:Int) < -b by omega)
rw [Int.mul_neg] at h; omega
rw [if_neg hab, if_neg (by omega), if_pos hb]; left; grind
· have hap : 0 < a := by omega
have hbp : 0 < b := by omega
have hab : a * b ≥ 0 := by have := Int.mul_pos hap hbp; omega
rw [if_pos hab, if_neg (by omega), if_neg (by omega)]; left; grind
have absId : ∀ z : Int, 0 ≤ z → {abs} z = z := by
intro z hz; unfold {abs}; rw [if_neg (by omega)]
have absNeg : ∀ z : Int, 0 ≤ z → {abs} (-z) = z := by
intro z hz; unfold {abs}; split <;> omega
rcases hcases with h | h
· rw [h]; exact absId _ hR
· rw [h]; exact absNeg _ hR
theorem {base}__frac_bound_leaf (a b Ut Ub q : Int)
(ha : a ≠ 0) (hb : b ≠ 0) (hUt : 0 < Ut) (hUb : 0 < Ub)
(hwlo : {abs} b * Ut * q ≤ {abs} a * Ub)
(hwhi : {abs} a * Ub < {abs} b * Ut * (q + 1)) :
{abs} (a * Ub - (if a * b ≥ 0 then (1:Int) else 0 - 1) * q * Ut * b)
* {abs} (b * Ub) * (Ub * Ub)
< Ut * Ub * ({abs} (b * Ub) * {abs} (b * Ub)) := by
have hET := {base}__abs_trunc_mag a b Ut Ub q ha hb hwlo
have hEB : {abs} (b * Ub) = {abs} b * Ub := by
unfold {abs}
rcases Int.lt_or_le b 0 with hbn | hbp
· rw [if_pos (by have := Int.mul_pos (show (0:Int) < -b by omega) hUb; rw [Int.neg_mul] at this; omega), if_pos hbn]; grind
· rw [if_neg (by have := Int.mul_nonneg hbp (Int.le_of_lt hUb); omega), if_neg (by omega)]
rw [hET, hEB]
have hbb : 0 < {abs} b := by unfold {abs}; split <;> omega
have hkey : {abs} a * Ub - {abs} b * Ut * q < {abs} b * Ut := by
have hexp : {abs} b * Ut * (q + 1) = {abs} b * Ut * q + {abs} b * Ut := by
rw [Int.mul_add, Int.mul_one]
omega
have hP : 0 < {abs} b * Ub * Ub * Ub :=
Int.mul_pos (Int.mul_pos (Int.mul_pos hbb hUb) hUb) hUb
have hLrw : ({abs} a * Ub - {abs} b * Ut * q) * ({abs} b * Ub) * (Ub * Ub)
= ({abs} a * Ub - {abs} b * Ut * q) * ({abs} b * Ub * Ub * Ub) := by grind
have hRrw : Ut * Ub * (({abs} b * Ub) * ({abs} b * Ub))
= ({abs} b * Ut) * ({abs} b * Ub * Ub * Ub) := by grind
rw [hLrw, hRrw]
exact Int.mul_lt_mul_of_pos_right hkey hP"#
)
}
fn render_frac_away_support(base: &str, abs: &str, floor: &str) -> String {
format!(
r#"theorem {base}__floordiv_eq (a d : Int) (hd : 0 < d) : {floor} a d = a / d := by
have hne : ¬((d == 0) = true) := by simp only [beq_iff_eq]; omega
simp only [{floor}]
rw [if_neg hne]
simp only [Except.withDefault]
theorem {base}__floor_window (a d : Int) (hd : 0 < d) :
d * ({floor} a d) ≤ a ∧ a < d * ({floor} a d + 1) := by
rw [{base}__floordiv_eq a d hd]
have hd0 : d ≠ 0 := by omega
have heq := Int.ediv_add_emod a d
have h0 := Int.emod_nonneg a hd0
have h1 := Int.emod_lt_of_pos a hd
have hexp : d * (a / d + 1) = d * (a / d) + d := by rw [Int.mul_add, Int.mul_one]
refine ⟨by omega, ?_⟩
rw [hexp]; omega
theorem {base}__abs_mag (a b Ut Ub q : Int) (ha0 : a ≠ 0) (hb0 : b ≠ 0) :
{abs} (a * Ub - (if a * b ≥ 0 then (1:Int) else 0 - 1) * q * Ut * b)
= {abs} ({abs} a * Ub - {abs} b * Ut * q) := by
have hcases :
a * Ub - (if a * b ≥ 0 then (1:Int) else 0 - 1) * q * Ut * b
= ({abs} a * Ub - {abs} b * Ut * q)
∨ a * Ub - (if a * b ≥ 0 then (1:Int) else 0 - 1) * q * Ut * b
= -({abs} a * Ub - {abs} b * Ut * q) := by
unfold {abs}
rcases Int.lt_or_le a 0 with ha | ha <;> rcases Int.lt_or_le b 0 with hb | hb
· have hab : a * b ≥ 0 := by
have h := Int.mul_pos (show (0:Int) < -a by omega) (show (0:Int) < -b by omega)
rw [Int.neg_mul, Int.mul_neg, Int.neg_neg] at h; omega
rw [if_pos hab, if_pos ha, if_pos hb]; right; grind
· have hbp : 0 < b := by omega
have hab : ¬ (a * b ≥ 0) := by
have h := Int.mul_pos (show (0:Int) < -a by omega) hbp
rw [Int.neg_mul] at h; omega
rw [if_neg hab, if_pos ha, if_neg (by omega)]; right; grind
· have hap : 0 < a := by omega
have hab : ¬ (a * b ≥ 0) := by
have h := Int.mul_pos hap (show (0:Int) < -b by omega)
rw [Int.mul_neg] at h; omega
rw [if_neg hab, if_neg (by omega), if_pos hb]; left; grind
· have hap : 0 < a := by omega
have hbp : 0 < b := by omega
have hab : a * b ≥ 0 := by have := Int.mul_pos hap hbp; omega
rw [if_pos hab, if_neg (by omega), if_neg (by omega)]; left; grind
rcases hcases with h | h
· rw [h]
· rw [h]; unfold {abs}; split <;> split <;> omega
theorem {base}__absNonpos (z : Int) (hz : z ≤ 0) : {abs} z = -z := by
unfold {abs}; split <;> omega
theorem {base}__absMul (b Ub : Int) (hUb : 0 < Ub) : {abs} (Ub * b) = {abs} b * Ub := by
unfold {abs}
rcases Int.lt_or_le b 0 with hbn | hbp
· rw [if_pos (by have := Int.mul_pos hUb (show (0:Int) < -b by omega); rw [Int.mul_neg] at this; omega), if_pos hbn]; grind
· rw [if_neg (by have := Int.mul_nonneg (Int.le_of_lt hUb) hbp; omega), if_neg (by omega)]; grind
theorem {base}__away_leaf (a b Ut Ub cq : Int)
(ha : a ≠ 0) (hb : b ≠ 0) (hUt : 0 < Ut) (hUb : 0 < Ub)
(hwlo : ({abs} b * Ut) * cq ≤ {abs} a * Ub + {abs} b * Ut - 1)
(hwhi : {abs} a * Ub + {abs} b * Ut - 1 < ({abs} b * Ut) * (cq + 1)) :
{abs} ((if a * b ≥ 0 then (1:Int) else 0 - 1) * cq * Ut * b - a * Ub)
* {abs} (Ub * b) * (Ub * Ub)
< Ut * Ub * ({abs} (Ub * b) * {abs} (Ub * b)) := by
have hflip : (if a * b ≥ 0 then (1:Int) else 0 - 1) * cq * Ut * b - a * Ub
= -(a * Ub - (if a * b ≥ 0 then (1:Int) else 0 - 1) * cq * Ut * b) := by grind
rw [hflip]
have habsneg : {abs} (-(a * Ub - (if a * b ≥ 0 then (1:Int) else 0 - 1) * cq * Ut * b))
= {abs} (a * Ub - (if a * b ≥ 0 then (1:Int) else 0 - 1) * cq * Ut * b) := by
unfold {abs}; split <;> split <;> omega
rw [habsneg, {base}__abs_mag a b Ut Ub cq ha hb]
have hexpHi : {abs} b * Ut * (cq + 1) = {abs} b * Ut * cq + {abs} b * Ut := by
rw [Int.mul_add, Int.mul_one]
have hRle : {abs} a * Ub - {abs} b * Ut * cq ≤ 0 := by omega
rw [{base}__absNonpos _ hRle, {base}__absMul b Ub hUb]
have hbb : 0 < {abs} b := by unfold {abs}; split <;> omega
have hkey : -({abs} a * Ub - {abs} b * Ut * cq) < {abs} b * Ut := by omega
have hP : 0 < {abs} b * Ub * Ub * Ub :=
Int.mul_pos (Int.mul_pos (Int.mul_pos hbb hUb) hUb) hUb
have hLrw : -({abs} a * Ub - {abs} b * Ut * cq) * ({abs} b * Ub) * (Ub * Ub)
= -({abs} a * Ub - {abs} b * Ut * cq) * ({abs} b * Ub * Ub * Ub) := by grind
have hRrw : Ut * Ub * (({abs} b * Ub) * ({abs} b * Ub))
= ({abs} b * Ut) * ({abs} b * Ub * Ub * Ub) := by grind
rw [hLrw, hRrw]
exact Int.mul_lt_mul_of_pos_right hkey hP"#
)
}
fn emit_rational_frac_bound(
vb: &VerifyBlock,
law: &VerifyLaw,
ctx: &CodegenContext,
intro_names: &[String],
) -> Option<AutoProof> {
use crate::ast::{BinOp, Expr};
let pow = rf_pow2_fn(vb, law, ctx)?;
let inlined = inline_fn_call(&law.lhs, ctx)?;
let Expr::FnCall(lt_callee, lt_args) = &inlined.node else {
return None;
};
if lt_args.len() != 2 {
return None;
}
let lessthan_lean = aver_name_to_lean(&super::super::shared::expr_dotted_name(lt_callee)?);
let Expr::FnCall(abs_callee, abs_args) = <_args[0].node else {
return None;
};
let absfrac_lean = aver_name_to_lean(&super::super::shared::expr_dotted_name(abs_callee)?);
let err_call = abs_args.first()?;
let Expr::FnCall(ps_callee, ps_args) = <_args[1].node else {
return None;
};
let ps_lean = aver_name_to_lean(&super::super::shared::expr_dotted_name(ps_callee)?);
let le = rf_emit(ctx, ps_args.first()?);
let Expr::FnCall(err_callee, te_args) = &err_call.node else {
return None;
};
if te_args.len() != 2 {
return None;
}
let errfn_lean = aver_name_to_lean(&super::super::shared::expr_dotted_name(err_callee)?);
let f_name = expr_var_name(&te_args[0].node)?.to_string();
let minus_inlined = rf_inline_fn_call(err_call, ctx)?;
let Expr::FnCall(minus_callee, m_args) = &minus_inlined.node else {
return None;
};
let minus_lean = aver_name_to_lean(&super::super::shared::expr_dotted_name(minus_callee)?);
if m_args.len() != 2 {
return None;
}
let is_val = |e: &Expr| expr_var_name(e) == Some(f_name.as_str());
let (rounded_call, is_away) = if is_val(&m_args[0].node) {
(&m_args[1], false)
} else if is_val(&m_args[1].node) {
(&m_args[0], true)
} else {
return None;
};
let Expr::FnCall(round_callee, _) = &rounded_call.node else {
return None;
};
let roundfn_lean = aver_name_to_lean(&super::super::shared::expr_dotted_name(round_callee)?);
let rounded_inl = rf_inline_fn_call(rounded_call, ctx)?;
let Expr::Match { arms, .. } = &rounded_inl.node else {
return None;
};
let record = arms.iter().find_map(|a| {
if let Expr::RecordCreate { fields, .. } = &a.body.node {
Some(fields)
} else {
None
}
})?;
let top = record.iter().find(|(n, _)| n == "top").map(|(_, e)| e)?;
let Expr::BinOp(BinOp::Mul, l, _ut) = &top.node else {
return None;
};
let Expr::BinOp(BinOp::Mul, sgn_e, floor_e) = &l.node else {
return None;
};
let Expr::FnCall(sgn_callee, _) = &sgn_e.node else {
return None;
};
let signfn_lean = aver_name_to_lean(&super::super::shared::expr_dotted_name(sgn_callee)?);
let Expr::FnCall(floor_callee, floor_args) = &floor_e.node else {
return None;
};
let floorfn_dotted = super::super::shared::expr_dotted_name(floor_callee)?;
let is_wrapper_body = |body: &Expr| {
matches!(body, Expr::FnCall(c, a)
if a.len() == 2
&& super::super::shared::expr_dotted_name(c).as_deref()
== Some("Result.withDefault"))
};
let floor_body = rf_resolve_fn(ctx, &floorfn_dotted)?
.body
.tail_expr()?
.clone();
let direct_wrapper = is_wrapper_body(&floor_body.node);
if direct_wrapper == is_away {
return None;
}
let (floorfn_lean, ceilfn_lean): (String, Option<String>) = if direct_wrapper {
(aver_name_to_lean(&floorfn_dotted), None)
} else {
let Expr::FnCall(inner_c, inner_a) = &floor_body.node else {
return None;
};
if inner_a.len() != 2 {
return None;
}
let inner_dotted = super::super::shared::expr_dotted_name(inner_c)?;
let inner_body = rf_resolve_fn(ctx, &inner_dotted)?.body.tail_expr()?.clone();
if !is_wrapper_body(&inner_body.node) {
return None;
}
(
aver_name_to_lean(&inner_dotted),
Some(aver_name_to_lean(&floorfn_dotted)),
)
};
let n_arg = floor_args.first()?;
let Expr::BinOp(BinOp::Mul, abs_call, _) = &n_arg.node else {
return None;
};
let Expr::FnCall(abs_callee2, _) = &abs_call.node else {
return None;
};
let absfn_lean = aver_name_to_lean(&super::super::shared::expr_dotted_name(abs_callee2)?);
let law_fn_lean = aver_name_to_lean(&vb.fn_name);
let val = aver_name_to_lean(&f_name);
let base = format!("{law_fn_lean}_law_{}__frb", law.name);
let mut support: Vec<String> =
super::super::floor_window::pow2_signed_pos_support(&base, &pow, &ps_lean)
.lines()
.map(|s| s.to_string())
.collect();
let extra = if is_away {
render_frac_away_support(&base, &absfn_lean, &floorfn_lean)
} else {
render_frac_bound_support(&base, &absfn_lean, &floorfn_lean)
};
support.extend(extra.lines().map(|s| s.to_string()));
let ut = format!("({ps_lean} ({le})).top");
let ub = format!("({ps_lean} ({le})).bottom");
let intro = format!(" intro {} h_when", intro_names.join(" "));
let mut body: Vec<String> = vec![intro, " first".to_string(), " | (".to_string()];
body.push(
" simp only [Bool.and_eq_true, bne_iff_ne, decide_eq_true_eq] at h_when".to_string(),
);
body.push(" obtain ⟨ha, hb⟩ := h_when".to_string());
let unfold_ceil = ceilfn_lean
.as_deref()
.map(|c| format!(" {c}"))
.unwrap_or_default();
body.push(format!(
" unfold {law_fn_lean} {errfn_lean} {roundfn_lean}{unfold_ceil}"
));
body.push(" rw [if_neg (by simp only [beq_iff_eq]; exact ha)]".to_string());
body.push(format!(
" simp only [{minus_lean}, {absfrac_lean}, {lessthan_lean}, {signfn_lean}, decide_eq_true_eq]"
));
body.push(format!(" have hUt : 0 < {ut} := {base}__sgnt_pos _"));
body.push(format!(" have hUb : 0 < {ub} := {base}__sgnb_pos _"));
body.push(format!(
" have hbb : 0 < {absfn_lean} {val}.bottom := by unfold {absfn_lean}; split <;> omega"
));
body.push(format!(
" have hD : 0 < {absfn_lean} {val}.bottom * {ut} := Int.mul_pos hbb hUt"
));
if is_away {
let numn = format!("{absfn_lean} {val}.top * {ub} + {absfn_lean} {val}.bottom * {ut} - 1");
let den = format!("{absfn_lean} {val}.bottom * {ut}");
body.push(format!(
" obtain ⟨hwlo, hwhi⟩ := {base}__floor_window ({numn}) ({den}) hD"
));
body.push(format!(
" exact {base}__away_leaf {val}.top {val}.bottom {ut} {ub} ({floorfn_lean} ({numn}) ({den})) ha hb hUt hUb hwlo hwhi"
));
} else {
let q = format!(
"{floorfn_lean} ({absfn_lean} {val}.top * {ub}) ({absfn_lean} {val}.bottom * {ut})"
);
body.push(format!(
" obtain ⟨hwlo, hwhi⟩ := {base}__floor_window ({absfn_lean} {val}.top * {ub}) ({absfn_lean} {val}.bottom * {ut}) hD"
));
body.push(format!(
" exact {base}__frac_bound_leaf {val}.top {val}.bottom {ut} {ub} ({q}) ha hb hUt hUb hwlo hwhi"
));
}
body.push(" )".to_string());
let floor = if super::super::super::tactic_ir::speculative::probing() {
let id = format!("{}.{}", vb.fn_name, law.name);
super::super::super::tactic_ir::speculative::record_probed(&id);
format!(" | (trace \"AVERSPEC_SORRY:{id}\"; sorry)")
} else {
" | sorry".to_string()
};
body.push(floor);
Some(AutoProof {
support_lines: support,
body: Tactic::raw(body),
replaces_theorem: false,
})
}
fn emit_rational_floor_bound_general(
vb: &VerifyBlock,
law: &VerifyLaw,
ctx: &CodegenContext,
intro_names: &[String],
) -> Option<AutoProof> {
use crate::ast::Expr;
let pow = rf_pow2_fn(vb, law, ctx)?;
let inlined = inline_fn_call(&law.lhs, ctx)?;
let Expr::FnCall(_lt, lt_args) = &inlined.node else {
return None;
};
if lt_args.len() != 2 {
return None;
}
let Expr::FnCall(_abs, abs_args) = <_args[0].node else {
return None;
};
let err_call = abs_args.first()?;
let Expr::FnCall(_ps, ps_args) = <_args[1].node else {
return None;
};
let le_expr = ps_args.first()?;
let Expr::FnCall(_te, te_args) = &err_call.node else {
return None;
};
if te_args.len() != 2 {
return None;
}
let f_name = expr_var_name(&te_args[0].node)?.to_string();
let i_name = expr_var_name(&te_args[1].node)?.to_string();
let minus_inlined = rf_inline_fn_call(err_call, ctx)?;
let Expr::FnCall(_m, m_args) = &minus_inlined.node else {
return None;
};
if m_args.len() != 2 {
return None;
}
let rounded_call = match &m_args[1].node {
Expr::FnCall(_, fv_args) => fv_args.first()?,
_ => return None,
};
let fpv_f = rf_inline_fn_call(&m_args[0], ctx)?;
let gf = rf_general_value_fields(&fpv_f, ctx, &pow)?;
let rounded_rec = rf_inline_fn_call(rounded_call, ctx)?;
let Expr::RecordCreate {
fields: rf_fields, ..
} = &rounded_rec.node
else {
return None;
};
let q_spanned = rf_fields
.iter()
.find(|(n, _)| n == "sigBits")
.map(|(_, e)| e)?;
let q = rf_emit(ctx, q_spanned);
let le = rf_emit(ctx, le_expr);
let (window_thm, window_fn) = rf_window_law(vb, law, ctx)?;
let hom = rf_homomorphism_name(ctx, &pow)?;
let when = law.when.as_ref()?;
let conjs = rf_flatten_bool_and(when);
let sign_conj = rf_sign_conjunct(law, ctx)?;
let fmt_idx = conjs
.iter()
.position(|c| arith_eq(&c.node, &sign_conj.node))?;
let isfp_lean = match &sign_conj.node {
Expr::FnCall(callee, _) => {
aver_name_to_lean(&super::super::shared::expr_dotted_name(callee)?)
}
_ => return None,
};
let n = conjs.len();
let mut pat = "h_rfbp0".to_string();
for k in 1..n {
pat = format!("⟨{pat}, h_rfbp{k}⟩");
}
let fmtname = format!("h_rfbp{fmt_idx}");
let sgn_base = rf_bare_basename(&gf.sgn_fn).to_string();
let defs = rf_filtered_defs(ctx, vb, law)
.into_iter()
.filter(|d| rf_bare_basename(d) != sgn_base)
.collect::<Vec<_>>()
.join(", ");
let base = format!("{}_law_{}__rfb", aver_name_to_lean(&vb.fn_name), law.name);
let support_lines: Vec<String> =
super::super::floor_window::pow2_signed_pos_support(&base, &pow, &gf.sgn_fn)
.lines()
.map(|l| l.to_string())
.collect();
let exp = gf.exp.clone();
let sgn = gf.sgn_fn.clone();
let pet = format!("({sgn} ({exp})).top");
let peb = format!("({sgn} ({exp})).bottom");
let plt = format!("({sgn} ({le})).top");
let plb = format!("({sgn} ({le})).bottom");
let pw = format!("{pow} ({} - 1)", gf.width);
let pp = format!("{pow} ({i_name} - 1)");
let sg = &gf.sign;
let s = &gf.sig;
let r = format!("{s} * {pp} - {pw} * {q}");
let minustop =
format!("{pet} * ({sg} * {s}) * ({peb} * {pp}) - {pet} * ({sg} * {q}) * ({peb} * {pw})");
let t_abs = format!("{pet} * {peb} * ({r})");
let absbot = format!("{peb} * {pw} * ({peb} * {pp})");
let m = format!("{pet} * {peb} * ({absbot}) * {plb} * {plb}");
let intro = format!(" intro {} h_when", intro_names.join(" "));
let mut body: Vec<String> = vec![intro, " first".to_string(), " | (".to_string()];
body.push(" simp only [Bool.and_eq_true, decide_eq_true_eq] at h_when".to_string());
body.push(format!(" obtain {pat} := h_when"));
body.push(format!(" have hpp : 0 < {pp} := {base}__pow_pos _"));
body.push(format!(" have hpw : 0 < {pw} := {base}__pow_pos _"));
body.push(format!(" have hPSeT : 0 < {pet} := {base}__sgnt_pos _"));
body.push(format!(" have hPSeB : 0 < {peb} := {base}__sgnb_pos _"));
body.push(format!(" have hPSlT : 0 < {plt} := {base}__sgnt_pos _"));
body.push(format!(" have hPSlB : 0 < {plb} := {base}__sgnb_pos _"));
body.push(format!(" have hsign : {sg} = 1 ∨ {sg} = -1 := by unfold {isfp_lean} at {fmtname}; simp only [Bool.and_eq_true, Bool.or_eq_true, beq_iff_eq, decide_eq_true_eq] at {fmtname}; exact {fmtname}.1"));
body.push(format!(" have h_win := {window_thm} {f_name} {i_name}"));
body.push(format!(
" simp only [{window_fn}, Bool.and_eq_true, decide_eq_true_eq] at h_win"
));
body.push(format!(
" have hr0 : 0 ≤ {r} := by have := h_win.1; omega"
));
body.push(format!(
" have hrW : {r} < {pw} := by have hexp : {pw} * ({q} + 1) = {pw} * {q} + {pw} := (by rw [Int.mul_add, Int.mul_one]); have := h_win.2; omega"
));
body.push(format!(
" have hlink : {pet} * {plb} = {plt} * {pp} * {peb} := by"
));
body.push(format!(" by_cases hE : {exp} < 0"));
body.push(format!(" · have hLE : {le} < 0 := by omega"));
body.push(format!(
" have hh := {hom} ({i_name} - 1) (0 - {exp}) (by simp only [Bool.and_eq_true, ge_iff_le, decide_eq_true_eq]; omega)"
));
body.push(format!(
" rw [show ({i_name} - 1) + (0 - {exp}) = 0 - ({le}) by omega] at hh"
));
body.push(format!(
" unfold {sgn}; rw [if_pos hE, if_pos hLE]; grind"
));
body.push(format!(" · by_cases hLE : {le} < 0"));
body.push(format!(
" · have hh := {hom} {exp} (0 - ({le})) (by simp only [Bool.and_eq_true, ge_iff_le, decide_eq_true_eq]; omega)"
));
body.push(format!(
" rw [show {exp} + (0 - ({le})) = {i_name} - 1 by omega] at hh"
));
body.push(format!(
" unfold {sgn}; rw [if_neg hE, if_pos hLE]; grind"
));
body.push(format!(
" · have hh := {hom} ({le}) ({i_name} - 1) (by simp only [Bool.and_eq_true, ge_iff_le, decide_eq_true_eq]; omega)"
));
body.push(format!(
" rw [show ({le}) + ({i_name} - 1) = {exp} by omega] at hh"
));
body.push(format!(
" unfold {sgn}; rw [if_neg hE, if_neg hLE]; grind"
));
body.push(format!(" simp only [{defs}, decide_eq_true_eq]"));
body.push(format!(
" have habsN : (if {minustop} < 0 then 0 - ({minustop}) else {minustop}) = {t_abs} := by have hfact : {minustop} = {sg} * ({t_abs}) := (by grind); have hnn : 0 ≤ {t_abs} := Int.mul_nonneg (Int.mul_nonneg (Int.le_of_lt hPSeT) (Int.le_of_lt hPSeB)) hr0; rw [hfact]; rcases hsign with h | h <;> rw [h] <;> split <;> omega"
));
body.push(" rw [habsN]".to_string());
body.push(format!(
" have hBotpos : 0 < {absbot} := by aver_int_order"
));
body.push(format!(" rw [if_neg (show ¬ ({absbot} < 0) by omega)]"));
body.push(format!(
" have hL : {t_abs} * ({absbot}) * ({plb} * {plb}) = ({m}) * ({r}) := by grind"
));
body.push(format!(
" have hR : {plt} * {plb} * (({absbot}) * ({absbot})) = ({m}) * {pw} := by grind"
));
body.push(" rw [hL, hR]".to_string());
body.push(" apply Int.mul_lt_mul_of_pos_left hrW".to_string());
body.push(" aver_int_order".to_string());
body.push(" )".to_string());
let floor = if super::super::super::tactic_ir::speculative::probing() {
let id = format!("{}.{}", vb.fn_name, law.name);
super::super::super::tactic_ir::speculative::record_probed(&id);
format!(" | (trace \"AVERSPEC_SORRY:{id}\"; sorry)")
} else {
" | sorry".to_string()
};
body.push(floor);
Some(AutoProof {
support_lines,
body: Tactic::raw(body),
replaces_theorem: false,
})
}
fn emit_rational_floor_sign_general(
vb: &VerifyBlock,
law: &VerifyLaw,
ctx: &CodegenContext,
intro_names: &[String],
) -> Option<AutoProof> {
use crate::ast::Expr;
let pow = rf_pow2_fn(vb, law, ctx)?;
let inlined = inline_fn_call(&law.lhs, ctx)?;
let Expr::Match { subject, arms } = &inlined.node else {
return None;
};
if arms.len() != 2 {
return None;
}
let Expr::FnCall(_nn, nn_args) = &subject.node else {
return None;
};
let value_call = nn_args.first()?;
let fpv_f = rf_inline_fn_call(value_call, ctx)?;
let gf = rf_general_value_fields(&fpv_f, ctx, &pow)?;
let Expr::FnCall(_, a0_args) = &arms[0].body.node else {
return None;
};
let err_call = a0_args.first()?;
let Expr::FnCall(_, te_args) = &err_call.node else {
return None;
};
if te_args.len() != 2 {
return None;
}
let f_name = expr_var_name(&te_args[0].node)?.to_string();
let i_name = expr_var_name(&te_args[1].node)?.to_string();
let minus_inlined = rf_inline_fn_call(err_call, ctx)?;
let Expr::FnCall(_m, m_args) = &minus_inlined.node else {
return None;
};
if m_args.len() != 2 {
return None;
}
let rounded_call = match &m_args[1].node {
Expr::FnCall(_, fv_args) => fv_args.first()?,
_ => return None,
};
let rounded_rec = rf_inline_fn_call(rounded_call, ctx)?;
let Expr::RecordCreate {
fields: rf_fields, ..
} = &rounded_rec.node
else {
return None;
};
let q_spanned = rf_fields
.iter()
.find(|(n, _)| n == "sigBits")
.map(|(_, e)| e)?;
let q = rf_emit(ctx, q_spanned);
let (window_thm, window_fn) = rf_window_law(vb, law, ctx)?;
let when = law.when.as_ref()?;
let conjs = rf_flatten_bool_and(when);
let sign_conj = rf_sign_conjunct(law, ctx)?;
let fmt_idx = conjs
.iter()
.position(|c| arith_eq(&c.node, &sign_conj.node))?;
let isfp_lean = match &sign_conj.node {
Expr::FnCall(callee, _) => {
aver_name_to_lean(&super::super::shared::expr_dotted_name(callee)?)
}
_ => return None,
};
let n = conjs.len();
let mut pat = "h_rfbp0".to_string();
for k in 1..n {
pat = format!("⟨{pat}, h_rfbp{k}⟩");
}
let fmtname = format!("h_rfbp{fmt_idx}");
let sgn_base = rf_bare_basename(&gf.sgn_fn).to_string();
let defs = rf_filtered_defs(ctx, vb, law)
.into_iter()
.filter(|d| rf_bare_basename(d) != sgn_base)
.collect::<Vec<_>>()
.join(", ");
let base = format!("{}_law_{}__rfs", aver_name_to_lean(&vb.fn_name), law.name);
let support_lines: Vec<String> =
super::super::floor_window::pow2_signed_pos_support(&base, &pow, &gf.sgn_fn)
.lines()
.map(|l| l.to_string())
.collect();
let exp = gf.exp.clone();
let pet = format!("({} ({exp})).top", gf.sgn_fn);
let peb = format!("({} ({exp})).bottom", gf.sgn_fn);
let pw = format!("{pow} ({} - 1)", gf.width);
let pp = format!("{pow} ({i_name} - 1)");
let sg = &gf.sign;
let s = &gf.sig;
let r = format!("{s} * {pp} - {pw} * {q}");
let minustop =
format!("{pet} * ({sg} * {s}) * ({peb} * {pp}) - {pet} * ({sg} * {q}) * ({peb} * {pw})");
let absbot = format!("{peb} * {pw} * ({peb} * {pp})");
let vprod = format!("{pet} * ({sg} * {s}) * ({peb} * {pw})");
let a_pos = format!("{pet} * {peb} * {s} * {pw}");
let teprod = format!("({minustop}) * ({absbot})");
let b_nn = format!("{pet} * {peb} * ({r}) * ({absbot})");
let intro = format!(" intro {} h_when", intro_names.join(" "));
let mut body: Vec<String> = vec![intro, " first".to_string(), " | (".to_string()];
body.push(" simp only [Bool.and_eq_true, decide_eq_true_eq] at h_when".to_string());
body.push(format!(" obtain {pat} := h_when"));
body.push(format!(" have hpp : 0 < {pp} := {base}__pow_pos _"));
body.push(format!(" have hpw : 0 < {pw} := {base}__pow_pos _"));
body.push(format!(" have hPSeT : 0 < {pet} := {base}__sgnt_pos _"));
body.push(format!(" have hPSeB : 0 < {peb} := {base}__sgnb_pos _"));
body.push(format!(" have hfpu := {fmtname}"));
body.push(format!(" unfold {isfp_lean} at hfpu"));
body.push(
" simp only [Bool.and_eq_true, Bool.or_eq_true, beq_iff_eq, decide_eq_true_eq] at hfpu"
.to_string(),
);
body.push(format!(" have hsign : {sg} = 1 ∨ {sg} = -1 := hfpu.1"));
body.push(format!(
" have hsig : 0 < {s} := by have := hfpu.2.1; omega"
));
body.push(format!(" have h_win := {window_thm} {f_name} {i_name}"));
body.push(format!(
" simp only [{window_fn}, Bool.and_eq_true, decide_eq_true_eq] at h_win"
));
body.push(format!(
" have hr0 : 0 ≤ {r} := by have := h_win.1; omega"
));
body.push(format!(" simp only [{defs}, decide_eq_true_eq]"));
body.push(format!(
" have hVfact : {vprod} = {sg} * ({a_pos}) := by grind"
));
body.push(format!(
" have hTfact : {teprod} = {sg} * ({b_nn}) := by grind"
));
body.push(format!(" have hVA : 0 < {a_pos} := by aver_int_order"));
body.push(format!(" have hTB : 0 ≤ {b_nn} := by aver_int_order"));
body.push(" simp only [hVfact, hTfact]".to_string());
body.push(" rcases hsign with h | h <;> simp only [h] <;> split <;> simp only [decide_eq_true_eq] <;> omega".to_string());
body.push(" )".to_string());
let floor = if super::super::super::tactic_ir::speculative::probing() {
let id = format!("{}.{}", vb.fn_name, law.name);
super::super::super::tactic_ir::speculative::record_probed(&id);
format!(" | (trace \"AVERSPEC_SORRY:{id}\"; sorry)")
} else {
" | sorry".to_string()
};
body.push(floor);
Some(AutoProof {
support_lines,
body: Tactic::raw(body),
replaces_theorem: false,
})
}
fn emit_rational_floor_bound(
vb: &VerifyBlock,
law: &VerifyLaw,
ctx: &CodegenContext,
intro_names: &[String],
) -> Option<AutoProof> {
use crate::ast::Expr;
if let Some(p) = emit_rational_frac_bound(vb, law, ctx, intro_names) {
return Some(p);
}
if let Some(p) = emit_rational_floor_bound_matched(vb, law, ctx, intro_names) {
return Some(p);
}
if let Some(p) = emit_rational_floor_bound_general(vb, law, ctx, intro_names) {
return Some(p);
}
let pow = rf_pow2_fn(vb, law, ctx)?;
let inlined = inline_fn_call(&law.lhs, ctx)?;
let Expr::FnCall(_lt, lt_args) = &inlined.node else {
return None;
};
if lt_args.len() != 2 {
return None;
}
let Expr::FnCall(_abs, abs_args) = <_args[0].node else {
return None;
};
let err_call = abs_args.first()?;
let Expr::FnCall(_ps, ps_args) = <_args[1].node else {
return None;
};
let le_expr = ps_args.first()?;
let Expr::FnCall(_te, te_args) = &err_call.node else {
return None;
};
if te_args.len() != 2 {
return None;
}
let f_name = expr_var_name(&te_args[0].node)?.to_string();
let i_name = expr_var_name(&te_args[1].node)?.to_string();
let minus_inlined = rf_inline_fn_call(err_call, ctx)?;
let Expr::FnCall(_m, m_args) = &minus_inlined.node else {
return None;
};
if m_args.len() != 2 {
return None;
}
let rounded_call = match &m_args[1].node {
Expr::FnCall(_, fv_args) => fv_args.first()?,
_ => return None,
};
let fpv_f = rf_inline_fn_call(&m_args[0], ctx)?;
let fields = rf_fpvalue_fields(&fpv_f, ctx)?;
let rounded_rec = rf_inline_fn_call(rounded_call, ctx)?;
let Expr::RecordCreate {
fields: rf_fields, ..
} = &rounded_rec.node
else {
return None;
};
let q_spanned = rf_fields
.iter()
.find(|(n, _)| n == "sigBits")
.map(|(_, e)| e)?;
let q = rf_emit(ctx, q_spanned);
let le = rf_emit(ctx, le_expr);
let (window_thm, window_fn) = rf_window_law(vb, law, ctx)?;
let hom = rf_homomorphism_name(ctx, &pow)?;
let defs = rf_filtered_defs(ctx, vb, law).join(", ");
let when = law.when.as_ref()?;
let conjs = rf_flatten_bool_and(when);
let sign_conj = rf_sign_conjunct(law, ctx)?;
let fmt_idx = conjs
.iter()
.position(|c| arith_eq(&c.node, &sign_conj.node))?;
let isfp_lean = match &sign_conj.node {
Expr::FnCall(callee, _) => {
aver_name_to_lean(&super::super::shared::expr_dotted_name(callee)?)
}
_ => return None,
};
let n = conjs.len();
let mut pat = "h_rfbp0".to_string();
for k in 1..n {
pat = format!("⟨{pat}, h_rfbp{k}⟩");
}
let fmtname = format!("h_rfbp{fmt_idx}");
let base = format!("{}_law_{}__rfb", aver_name_to_lean(&vb.fn_name), law.name);
let support_lines: Vec<String> = super::super::floor_window::pow_pos_support(&base, &pow)
.lines()
.map(|l| l.to_string())
.collect();
let pe = format!("{pow} ({})", fields.exp);
let pw = format!("{pow} ({} - 1)", fields.width);
let pp = format!("{pow} ({i_name} - 1)");
let pr = format!("{pow} (0 - ({le}))");
let ple = format!("{pow} ({le})");
let sgn = &fields.sign;
let sig = &fields.sig;
let exa = &fields.exp;
let r = format!("{sig} * {pp} - {pw} * {q}");
let num = format!("{sgn} * {sig} * {pe} * {pp} - {sgn} * {q} * {pe} * {pw}");
let intro = format!(" intro {} h_when", intro_names.join(" "));
let mut body: Vec<String> = vec![intro, " first".to_string(), " | (".to_string()];
body.push(" simp only [Bool.and_eq_true, decide_eq_true_eq] at h_when".to_string());
body.push(format!(" obtain {pat} := h_when"));
body.push(format!(" have hP : 0 < {pp} := {base}__pow_pos _"));
body.push(format!(" have hW : 0 < {pw} := {base}__pow_pos _"));
body.push(format!(" have hpe : 0 < {pe} := {base}__pow_pos _"));
body.push(format!(
" have hWP : 0 < {pw} * {pp} := Int.mul_pos hW hP"
));
body.push(format!(" have h_win := {window_thm} {f_name} {i_name}"));
body.push(format!(
" simp only [{window_fn}, Bool.and_eq_true, decide_eq_true_eq] at h_win"
));
body.push(format!(
" have hr0 : 0 ≤ {r} := by have := h_win.1; omega"
));
body.push(format!(
" have hrW : {r} < {pw} := by have hexp : {pw} * ({q} + 1) = {pw} * {q} + {pw} := (by rw [Int.mul_add, Int.mul_one]); have := h_win.2; omega"
));
body.push(format!(" have hsign : {sgn} = 1 ∨ {sgn} = -1 := by unfold {isfp_lean} at {fmtname}; simp only [Bool.and_eq_true, Bool.or_eq_true, beq_iff_eq, decide_eq_true_eq] at {fmtname}; exact {fmtname}.1"));
body.push(format!(" simp only [{defs}, decide_eq_true_eq]"));
body.push(format!(
" have habsN : (if {num} < 0 then 0 - ({num}) else {num}) = {pe} * ({r}) := by have hfact : {num} = {sgn} * ({pe} * ({r})) := (by grind); have hnn : 0 ≤ {pe} * ({r}) := Int.mul_nonneg (by omega) hr0; rw [hfact]; rcases hsign with h | h <;> rw [h] <;> split <;> omega"
));
body.push(" rw [habsN]".to_string());
body.push(format!(
" rw [if_neg (show ¬ ({pw} * {pp} < 0) by omega)]"
));
body.push(format!(" by_cases hk : {le} < 0"));
body.push(" · rw [if_pos hk]; dsimp only".to_string());
body.push(format!(" have hRb : 0 < {pr} := {base}__pow_pos _"));
body.push(format!(
" have hlink : {pe} * {pr} = {pp} := by have h := {hom} ({exa}) (0 - ({le})) (by simp only [Bool.and_eq_true, ge_iff_le, decide_eq_true_eq]; omega); rw [show {exa} + (0 - ({le})) = {i_name} - 1 by omega] at h; omega"
));
body.push(format!(
" have lhs_eq : {pe} * ({r}) * ({pw} * {pp}) * ({pr} * {pr}) = {pe} * ({pw} * {pp}) * ({pr} * {pr}) * ({r}) := by grind"
));
body.push(format!(
" have rhs_eq : 1 * {pr} * (({pw} * {pp}) * ({pw} * {pp})) = {pe} * ({pw} * {pp}) * ({pr} * {pr}) * {pw} := by rw [← hlink]; grind"
));
body.push(" rw [lhs_eq, rhs_eq]; aver_int_order".to_string());
body.push(" · rw [if_neg hk]; dsimp only".to_string());
body.push(format!(
" have hlink : {ple} * {pp} = {pe} := by have h := {hom} ({le}) ({i_name} - 1) (by simp only [Bool.and_eq_true, ge_iff_le, decide_eq_true_eq]; omega); rw [show ({le}) + ({i_name} - 1) = {exa} by omega] at h; omega"
));
body.push(format!(
" have lhs_eq : {pe} * ({r}) * ({pw} * {pp}) * (1 * 1) = {pe} * ({pw} * {pp}) * ({r}) := by grind"
));
body.push(format!(
" have rhs_eq : {ple} * 1 * (({pw} * {pp}) * ({pw} * {pp})) = {pe} * ({pw} * {pp}) * {pw} := by rw [← hlink]; grind"
));
body.push(" rw [lhs_eq, rhs_eq]; aver_int_order".to_string());
body.push(" )".to_string());
let floor = if super::super::super::tactic_ir::speculative::probing() {
let id = format!("{}.{}", vb.fn_name, law.name);
super::super::super::tactic_ir::speculative::record_probed(&id);
format!(" | (trace \"AVERSPEC_SORRY:{id}\"; sorry)")
} else {
" | sorry".to_string()
};
body.push(floor);
Some(AutoProof {
support_lines,
body: Tactic::raw(body),
replaces_theorem: false,
})
}
pub(super) fn emit_rational_floor_family(
vb: &VerifyBlock,
law: &VerifyLaw,
ctx: &CodegenContext,
intro_names: &[String],
) -> Option<AutoProof> {
let shape = rational_floor_shape(vb, law, ctx)?;
if shape == RationalFloorShape::Bound {
return emit_rational_floor_bound(vb, law, ctx, intro_names);
}
if shape == RationalFloorShape::Sign
&& let Some(p) = emit_rational_floor_sign_general(vb, law, ctx, intro_names)
{
return Some(p);
}
let pow = rf_pow2_fn(vb, law, ctx)?;
let citations = rf_citations(vb, law, ctx, intro_names);
let defs = rf_filtered_defs(ctx, vb, law).join(", ");
let intro = format!(" intro {} h_when", intro_names.join(" "));
let mut steps: Vec<String> = Vec::new();
for c in &citations {
steps.push(format!("have {} := {}", c.have_name, c.apply));
steps.push(format!("simp only [{}] at {}", c.simp_set, c.have_name));
}
let mut support_lines: Vec<String> = Vec::new();
let arm: String = match shape {
RationalFloorShape::NonnegPos => {
let base = format!("{}_law_{}__rfpp", aver_name_to_lean(&vb.fn_name), law.name);
support_lines = super::super::floor_window::pow_pos_support(&base, &pow)
.lines()
.map(|l| l.to_string())
.collect();
let mut idx = 0usize;
let mut push_pow = |steps: &mut Vec<String>, expr: String| {
steps.push(format!("have h_rfpp{idx} := {base}__pow_pos ({expr})"));
idx += 1;
};
for g in &law.givens {
let gl = aver_name_to_lean(&g.name);
if g.type_name.trim() == "Int" {
push_pow(&mut steps, gl.clone());
push_pow(&mut steps, format!("{gl} - 1"));
} else {
for fld in rf_record_int_fields(ctx, &g.type_name) {
push_pow(&mut steps, format!("{gl}.{fld}"));
push_pow(&mut steps, format!("{gl}.{fld} - 1"));
}
}
}
let close = format!(
"simp only [{defs}, Bool.and_eq_true, Bool.or_eq_true, ge_iff_le, gt_iff_lt, decide_eq_true_eq, beq_iff_eq] at h_when ⊢ <;> aver_int_order"
);
steps.push(close);
format!(" | ({})", steps.join("; "))
}
RationalFloorShape::Sign => {
let sign_conj = rf_sign_conjunct(law, ctx)?;
let when = law.when.as_ref()?;
let prem = rf_premise_proof(when, sign_conj)?;
steps.push(format!("have h_rfsign := {prem}"));
steps.push(format!(
"simp only [{defs}, Bool.and_eq_true, Bool.or_eq_true, decide_eq_true_eq, beq_iff_eq] at h_rfsign"
));
steps.push(format!("simp only [{defs}]"));
steps.push("rcases h_rfsign.1 with hs | hs <;> simp only [hs] <;> grind".to_string());
format!(" | ({})", steps.join("; "))
}
RationalFloorShape::Bound => {
unreachable!("Bound shape handled by emit_rational_floor_bound")
}
};
let floor = if super::super::super::tactic_ir::speculative::probing() {
let id = format!("{}.{}", vb.fn_name, law.name);
super::super::super::tactic_ir::speculative::record_probed(&id);
format!(" | (trace \"AVERSPEC_SORRY:{id}\"; sorry)")
} else {
" | sorry".to_string()
};
Some(AutoProof {
support_lines,
body: Tactic::raw(vec![intro, " first".to_string(), arm, floor]),
replaces_theorem: false,
})
}