use super::AutoProof;
use super::aver_name_to_lean;
use super::shared::{self, expr_dotted_name, find_fn_def_by_call_name};
use crate::ast::{Expr, FnDef, Spanned, Stmt, VerifyBlock, VerifyKind, VerifyLaw};
use crate::codegen::CodegenContext;
pub(super) struct FracOrderChain {
a: String,
m: String,
k: String,
small: String,
big: String,
isnonneg: String,
minus: String,
times: String,
lessthan: String,
samevalue: String,
sgn: String,
pow: String,
mono_def: String,
mono_thm: String,
cited_deps: Vec<(String, String)>,
}
fn substitute(
e: &Spanned<Expr>,
map: &std::collections::HashMap<String, Spanned<Expr>>,
) -> Spanned<Expr> {
let node = match &e.node {
Expr::Ident(n) | Expr::Resolved { name: n, .. } => {
if let Some(rep) = map.get(n) {
return rep.clone();
}
e.node.clone()
}
Expr::BinOp(op, a, b) => Expr::BinOp(
*op,
Box::new(substitute(a, map)),
Box::new(substitute(b, map)),
),
Expr::Neg(a) => Expr::Neg(Box::new(substitute(a, map))),
Expr::Attr(b, f) => Expr::Attr(Box::new(substitute(b, map)), f.clone()),
Expr::FnCall(c, args) => Expr::FnCall(
Box::new(substitute(c, map)),
args.iter().map(|x| substitute(x, map)).collect(),
),
other => other.clone(),
};
Spanned::bare(node)
}
fn signed_pow2_pow(fd: &FnDef, ctx: &CodegenContext) -> Option<String> {
use crate::ast::{BinOp, Literal};
let [(p, ty)] = fd.params.as_slice() else {
return None;
};
if ty.trim() != "Int" || fd.return_type.rsplit('.').next() != Some("Fraction") {
return None;
}
let [Stmt::Expr(body)] = fd.body.stmts() else {
return None;
};
let Expr::Match { subject, arms } = &body.node else {
return None;
};
let Expr::BinOp(BinOp::Lt, sl, sr) = &subject.node else {
return None;
};
if expr_dotted_name(sl).as_deref() != Some(p.as_str())
|| !matches!(&sr.node, Expr::Literal(Literal::Int(0)))
|| arms.len() != 2
{
return None;
}
let recursive = crate::codegen::lean::recursive_pure_fn_names(ctx);
let mut called: Vec<String> = Vec::new();
for arm in arms {
shared::collect_fncall_names(&arm.body.node, &mut called);
}
let mut pows: Vec<String> = called
.into_iter()
.map(|n| n.rsplit('.').next().unwrap_or(&n).to_string())
.filter(|n| recursive.contains(n))
.collect();
pows.sort();
pows.dedup();
match pows.as_slice() {
[single] => Some(single.clone()),
_ => None,
}
}
struct PoolLaw {
prefix: String,
theorem_base: String,
theorem_lean: String,
def_lean: String,
}
fn body_matches_monotone(ctx: &CodegenContext, subj_src: &str, sgn_short: &str) -> bool {
let Some(fd) = find_fn_def_by_call_name(ctx, subj_src) else {
return false;
};
if fd.return_type.trim() != "Bool" {
return false;
}
let [Stmt::Expr(body)] = fd.body.stmts() else {
return false;
};
let is_sgn = |e: &Spanned<Expr>| -> bool {
matches!(shared::short_call_name_args(e), Some((s, a)) if s == sgn_short && a.len() == 1)
};
let Some(nn) = shared::call_named(body, "isNonNeg", 1) else {
return false;
};
let Some(m) = shared::call_named(&nn[0], "minus", 2) else {
return false;
};
is_sgn(&m[0]) && is_sgn(&m[1])
}
fn find_monotone_law(ctx: &CodegenContext, sgn_short: &str) -> Option<PoolLaw> {
for module in &ctx.modules {
for vb in &module.verify_laws {
let VerifyKind::Law(law) = &vb.kind else {
continue;
};
if !matches!(law.rhs.node, Expr::Literal(crate::ast::Literal::Bool(true))) {
continue;
}
if !body_matches_monotone(ctx, &vb.fn_name, sgn_short) {
continue;
}
let base = format!(
"{}_law_{}",
aver_name_to_lean(&vb.fn_name),
aver_name_to_lean(&law.name)
);
return Some(PoolLaw {
prefix: module.prefix.clone(),
theorem_base: base.clone(),
theorem_lean: base,
def_lean: format!("{}.{}", module.prefix, aver_name_to_lean(&vb.fn_name)),
});
}
}
None
}
pub(super) fn recognize_frac_order_chain_shape(
_vb: &VerifyBlock,
law: &VerifyLaw,
ctx: &CodegenContext,
) -> Option<FracOrderChain> {
law.when.as_ref()?;
if !matches!(law.rhs.node, Expr::Literal(crate::ast::Literal::Bool(true))) {
return None;
}
let Expr::FnCall(callee, call_args) = &law.lhs.node else {
return None;
};
let subject_src = expr_dotted_name(callee)?;
let subj_fd = find_fn_def_by_call_name(ctx, &subject_src)?;
if subj_fd.return_type.trim() != "Bool"
|| subj_fd.params.len() != call_args.len()
|| !subj_fd.effects.is_empty()
{
return None;
}
let [Stmt::Expr(body)] = subj_fd.body.stmts() else {
return None;
};
let nn = shared::call_named(body, "isNonNeg", 1)?;
let Expr::FnCall(isnonneg_callee, _) = &body.node else {
return None;
};
let isnonneg = aver_name_to_lean(&expr_dotted_name(isnonneg_callee)?);
let m = shared::call_named(&nn[0], "minus", 2)?;
let Expr::FnCall(minus_callee, _) = &nn[0].node else {
return None;
};
let minus = aver_name_to_lean(&expr_dotted_name(minus_callee)?);
let rat_prefix = minus.rsplit_once('.').map(|(p, _)| p.to_string())?;
let times = format!("{rat_prefix}.times");
let samevalue = format!("{rat_prefix}.sameValue");
let (sgn_short, hi_args) = shared::short_call_name_args(&m[0])?;
if hi_args.len() != 1 {
return None;
}
let Expr::FnCall(sgn_callee, _) = &m[0].node else {
return None;
};
let sgn_dotted = expr_dotted_name(sgn_callee)?;
let sgn_fd = find_fn_def_by_call_name(ctx, &sgn_dotted)?;
let pow = signed_pow2_pow(sgn_fd, ctx)?;
let mut map: std::collections::HashMap<String, Spanned<Expr>> =
std::collections::HashMap::new();
for ((pname, _), arg) in subj_fd.params.iter().zip(call_args.iter()) {
map.insert(pname.clone(), arg.clone());
}
let big_e = substitute(&hi_args[0], &map);
let a_e = substitute(&m[1], &map);
let render = |e: &Spanned<Expr>| super::super::expr::emit_expr_legacy(e, ctx, None);
let a_lean = render(&a_e);
let big_lean = render(&big_e);
let lessthan = format!("{rat_prefix}.lessThan");
let when = law.when.as_ref()?;
let conj = shared::collect_when_clauses(when);
if conj.len() != 4 {
return None;
}
let mut from_h1: Option<(String, String)> = None; let mut from_h2: Option<(String, String)> = None; let mut nonzero_m: Option<String> = None;
let mut have_placement = false;
for c in &conj {
if let Some(nn1) = shared::call_named(c, "isNonNeg", 1)
&& let Some(m1) = shared::call_named(&nn1[0], "minus", 2)
&& let Some(t1) = shared::call_named(&m1[0], "times", 2)
&& render(&m1[1]) == a_lean
{
let k_str = render(&t1[0]);
let kc: String = k_str.chars().filter(|ch| !ch.is_whitespace()).collect();
if !(kc.contains("top:=2") && kc.contains("bottom:=1")) {
return None;
}
from_h1 = Some((k_str, render(&t1[1])));
continue;
}
if let Some(lt) = shared::call_named(c, "lessThan", 2)
&& let Some((s, sa)) = shared::short_call_name_args(<[1])
&& s == sgn_short
&& sa.len() == 1
{
from_h2 = Some((render(<[0]), render(&sa[0])));
continue;
}
if let Expr::BinOp(crate::ast::BinOp::Neq, l, r) = &c.node
&& matches!(r.node, Expr::Literal(crate::ast::Literal::Int(0)))
&& let Expr::Attr(base, field) = &l.node
&& field == "bottom"
{
nonzero_m = Some(render(base));
continue;
}
if matches!(
&c.node,
Expr::BinOp(crate::ast::BinOp::Lte | crate::ast::BinOp::Gte, _, _)
) {
have_placement = true;
continue;
}
return None;
}
let (k_lean, m_h1) = from_h1?;
let (m_h2, small_lean) = from_h2?;
let nz = nonzero_m?;
if m_h1 != m_h2 || m_h1 != nz || !have_placement {
return None;
}
let m_lean = m_h1;
let mono = find_monotone_law(ctx, &sgn_short)?;
Some(FracOrderChain {
a: a_lean,
m: m_lean,
k: k_lean,
small: small_lean,
big: big_lean,
isnonneg,
minus,
times,
lessthan,
samevalue,
sgn: aver_name_to_lean(&sgn_dotted),
pow: aver_name_to_lean(&pow),
mono_def: mono.def_lean,
mono_thm: mono.theorem_lean,
cited_deps: vec![(mono.prefix, mono.theorem_base)],
})
}
pub(in crate::codegen::lean) fn recognize_frac_order_chain(
vb: &VerifyBlock,
law: &VerifyLaw,
ctx: &CodegenContext,
) -> bool {
recognize_frac_order_chain_shape(vb, law, ctx).is_some()
}
pub(in crate::codegen::lean) fn frac_order_chain_cited_deps(
vb: &VerifyBlock,
law: &VerifyLaw,
ctx: &CodegenContext,
) -> Vec<(String, String)> {
recognize_frac_order_chain_shape(vb, law, ctx)
.map(|c| c.cited_deps)
.unwrap_or_default()
}
pub(super) fn emit_frac_order_chain_law(
vb: &VerifyBlock,
law: &VerifyLaw,
ctx: &CodegenContext,
theorem_base: &str,
quant_params: &str,
) -> Option<AutoProof> {
let c = recognize_frac_order_chain_shape(vb, law, ctx)?;
let render = |e: &Spanned<Expr>| super::super::expr::emit_expr_legacy(e, ctx, None);
let when = render(law.when.as_ref()?);
let lhs = render(&law.lhs);
let subject = aver_name_to_lean(&expr_dotted_name(&{
let Expr::FnCall(callee, _) = &law.lhs.node else {
return None;
};
(**callee).clone()
})?);
let intros: Vec<String> = law
.givens
.iter()
.map(|g| aver_name_to_lean(&g.name))
.collect();
let text = render_chain(
theorem_base,
quant_params,
&intros.join(" "),
&when,
&lhs,
&subject,
&c,
);
Some(AutoProof {
support_lines: text.lines().map(|l| l.to_string()).collect(),
body: crate::codegen::lean::tactic_ir::Tactic::raw(Vec::new()),
replaces_theorem: true,
})
}
#[allow(clippy::too_many_arguments)]
fn render_chain(
base: &str,
quant_params: &str,
intros: &str,
when: &str,
lhs: &str,
subject: &str,
c: &FracOrderChain,
) -> String {
let FracOrderChain {
a,
m,
k,
small,
big,
isnonneg,
minus,
times,
lessthan,
samevalue,
sgn,
pow,
mono_def,
mono_thm,
..
} = c;
let p = format!("{base}__");
let kit = format!(
r#"theorem {p}sq_nonneg (x : Int) : 0 ≤ x * x := by
cases Int.le_total 0 x with
| inl h => exact Int.mul_nonneg h h
| inr h => exact Int.mul_nonneg_of_nonpos_of_nonpos h h
theorem {p}sq_pos {{x : Int}} (hx : x ≠ 0) : 0 < x * x := by
cases Int.lt_or_gt_of_ne hx with
| inl h => exact Int.mul_pos_of_neg_of_neg h h
| inr h => exact Int.mul_pos h h
theorem {p}frac_le_trans (a b cc : Fraction) (hb : b.bottom ≠ 0)
(hab : {isnonneg} ({minus} b a) = true) (hbc : {isnonneg} ({minus} cc b) = true) :
{isnonneg} ({minus} cc a) = true := by
simp only [{isnonneg}, {minus}, decide_eq_true_eq, ge_iff_le] at hab hbc ⊢
have hP : 0 ≤ ((b.top*a.bottom - a.top*b.bottom) * (b.bottom*a.bottom)) * (cc.bottom*cc.bottom) :=
Int.mul_nonneg hab ({p}sq_nonneg cc.bottom)
have hQ : 0 ≤ ((cc.top*b.bottom - b.top*cc.bottom) * (cc.bottom*b.bottom)) * (a.bottom*a.bottom) :=
Int.mul_nonneg hbc ({p}sq_nonneg a.bottom)
have hid : ((b.top*a.bottom - a.top*b.bottom) * (b.bottom*a.bottom)) * (cc.bottom*cc.bottom)
+ ((cc.top*b.bottom - b.top*cc.bottom) * (cc.bottom*b.bottom)) * (a.bottom*a.bottom)
= (b.bottom*b.bottom) * ((cc.top*a.bottom - a.top*cc.bottom) * (cc.bottom*a.bottom)) := by
grind
have hsum : 0 ≤ (b.bottom*b.bottom) * ((cc.top*a.bottom - a.top*cc.bottom) * (cc.bottom*a.bottom)) := by
rw [← hid]; omega
exact Int.nonneg_of_mul_nonneg_right hsum ({p}sq_pos hb)
theorem {p}frac_lt_imp_le (a b : Fraction) (h : {lessthan} a b = true) :
{isnonneg} ({minus} b a) = true := by
simp only [{lessthan}, {isnonneg}, {minus}, decide_eq_true_eq, ge_iff_le] at h ⊢
have hid : (b.top*a.bottom - a.top*b.bottom) * (b.bottom*a.bottom)
= (b.top*b.bottom)*(a.bottom*a.bottom) - (a.top*a.bottom)*(b.bottom*b.bottom) := by grind
omega
theorem {p}frac_le_mul_pos (kk a b : Fraction) (hkt : 0 < kk.top) (hkb : 0 < kk.bottom)
(h : {isnonneg} ({minus} b a) = true) :
{isnonneg} ({minus} ({times} kk b) ({times} kk a)) = true := by
simp only [{isnonneg}, {minus}, {times}, decide_eq_true_eq, ge_iff_le] at h ⊢
have hid : ((kk.top*b.top)*(kk.bottom*a.bottom) - (kk.top*a.top)*(kk.bottom*b.bottom))
* ((kk.bottom*b.bottom)*(kk.bottom*a.bottom))
= ((kk.top*kk.bottom)*(kk.bottom*kk.bottom))
* ((b.top*a.bottom - a.top*b.bottom) * (b.bottom*a.bottom)) := by grind
have hkpos : 0 ≤ (kk.top*kk.bottom)*(kk.bottom*kk.bottom) :=
Int.mul_nonneg (Int.mul_nonneg (Int.le_of_lt hkt) (Int.le_of_lt hkb)) ({p}sq_nonneg kk.bottom)
rw [hid]
exact Int.mul_nonneg hkpos h
theorem {p}frac_le_samevalue_right (a b b' : Fraction)
(hb : b.bottom ≠ 0)
(hbb' : {samevalue} b b' = true)
(h : {isnonneg} ({minus} b a) = true) :
{isnonneg} ({minus} b' a) = true := by
simp only [{isnonneg}, {minus}, {samevalue}, decide_eq_true_eq, ge_iff_le, beq_iff_eq] at h hbb' ⊢
have hid : ((b'.top*a.bottom - a.top*b'.bottom)*(b'.bottom*a.bottom)) * (b.bottom*b.bottom)
= ((b.top*a.bottom - a.top*b.bottom)*(b.bottom*a.bottom)) * (b'.bottom*b'.bottom) := by
grind
have hrhs : 0 ≤ ((b.top*a.bottom - a.top*b.bottom)*(b.bottom*a.bottom)) * (b'.bottom*b'.bottom) :=
Int.mul_nonneg h ({p}sq_nonneg b'.bottom)
rw [← hid] at hrhs
exact Int.nonneg_of_mul_nonneg_left hrhs ({p}sq_pos hb)
theorem {p}pow_of_nonpos (n : Int) (h : n <= 0) : {pow} n = 1 := by
rw [{pow}.eq_def, if_pos h]
theorem {p}pow_of_pos (n : Int) (h : ¬n <= 0) : {pow} n = 2 * {pow} (n - 1) := by
rw [{pow}.eq_def, if_neg h]
theorem {p}pow_pos : ∀ (n : Int), 1 <= {pow} n := by
intro n
induction n using {pow}.induct with
| case1 n h => rw [{p}pow_of_nonpos n h]; omega
| case2 n h ih => rw [{p}pow_of_pos n h]; omega
theorem {p}pow2Signed_bottom_pos (kk : Int) : 0 < ({sgn} kk).bottom := by
unfold {sgn}
split
· show 0 < {pow} (0 - kk)
have := {p}pow_pos (0 - kk); omega
· show (0 : Int) < 1
decide
theorem {p}pow2Signed_double (kk : Int) :
{samevalue} ({times} ({k}) ({sgn} kk)) ({sgn} (kk+1)) = true := by
rcases (by omega : kk < 0 ∨ 0 <= kk) with h0 | h0
· rcases (by omega : kk + 1 < 0 ∨ kk = -1) with h1 | h1
· simp only [{samevalue}, {times}, {sgn}, if_pos h0, if_pos h1, beq_iff_eq]
have hd := {p}pow_of_pos (0 - kk) (by omega)
rw [show (0 - kk) - 1 = 0 - (kk + 1) by omega] at hd
rw [hd]; omega
· subst h1
simp only [{samevalue}, {times}, {sgn}, if_pos (show (-1:Int) < 0 by omega),
if_neg (show ¬ ((-1:Int) + 1 < 0) by omega), beq_iff_eq]
have ea : {pow} (0 - -1) = 2 := by
rw [show (0:Int) - -1 = 1 by omega]
have h := {p}pow_of_pos 1 (by omega)
rw [show (1:Int) - 1 = 0 by omega, {p}pow_of_nonpos 0 (by omega)] at h
omega
have eb : {pow} (-1 + 1) = 1 := by
rw [show (-1:Int) + 1 = 0 by omega, {p}pow_of_nonpos 0 (by omega)]
rw [ea, eb]; omega
· have h0' : ¬ (kk < 0) := by omega
have h1 : ¬ (kk + 1 < 0) := by omega
simp only [{samevalue}, {times}, {sgn}, if_neg h0', if_neg h1, beq_iff_eq]
have hd := {p}pow_of_pos (kk + 1) (by omega)
rw [show (kk + 1) - 1 = kk by omega] at hd
rw [hd]; omega"#
);
let assembly = format!(
r#"set_option maxHeartbeats 4000000 in
theorem {base} : ∀ {quant_params}, {when} = true -> {lhs} = true := by
intro {intros} h_when
first
| (simp only [Bool.and_eq_true, decide_eq_true_eq, bne_iff_ne, ne_eq] at h_when
obtain ⟨⟨⟨h1, h2⟩, hsd2⟩, h3⟩ := h_when
simp only [_root_.{subject}]
have hA := {p}frac_lt_imp_le ({m}) ({sgn} ({small})) h2
have hScaled := {p}frac_le_mul_pos ({k}) ({m}) ({sgn} ({small})) (by decide) (by decide) hA
have hDbl := {p}pow2Signed_double ({small})
have hb_ne : ({times} ({k}) ({sgn} ({small}))).bottom ≠ 0 := by
simp only [{times}, Int.one_mul]; exact Int.ne_of_gt ({p}pow2Signed_bottom_pos ({small}))
have link2 := {p}frac_le_samevalue_right ({times} ({k}) ({m})) ({times} ({k}) ({sgn} ({small}))) ({sgn} (({small})+1)) hb_ne hDbl hScaled
have Lmid_ne : ({times} ({k}) ({m})).bottom ≠ 0 := by
simp only [{times}, Int.one_mul]; exact hsd2
have L := {p}frac_le_trans ({a}) ({times} ({k}) ({m})) ({sgn} (({small})+1)) Lmid_ne h1 link2
have Mmono := {mono_thm} (({small})+1) ({big}) (by first
| omega
| (simp only [eq_iff_iff, iff_true]; omega)
| (simp only [decide_eq_true_eq]; omega))
simp only [{mono_def}] at Mmono
have Mmid_ne : ({sgn} (({small})+1)).bottom ≠ 0 := Int.ne_of_gt ({p}pow2Signed_bottom_pos (({small})+1))
exact {p}frac_le_trans ({a}) ({sgn} (({small})+1)) ({sgn} ({big})) Mmid_ne L Mmono)
| sorry"#
);
format!("{kit}\n{assembly}")
}