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, Spanned, Stmt, VerifyBlock, VerifyLaw};
use crate::codegen::CodegenContext;
pub(super) struct IntervalMono {
d: String,
lo: String,
hi: String,
v: String,
e: String,
subject: String,
}
fn match_abs_magnitude(expr: &Spanned<Expr>) -> Option<(String, String)> {
let abs_args = shared::call_named(expr, "absFraction", 1)?;
let minus_args = shared::call_named(&abs_args[0], "minus", 2)?;
let times_args = shared::call_named(&minus_args[0], "times", 2)?;
shared::call_named(&minus_args[1], "oneFraction", 0)?;
let x = shared::ident_name(×_args[0])?.to_string();
let y = shared::ident_name(×_args[1])?.to_string();
Some((x, y))
}
pub(super) fn recognize_interval_monotonicity_shape(
_vb: &VerifyBlock,
law: &VerifyLaw,
ctx: &CodegenContext,
) -> Option<IntervalMono> {
if law.givens.len() != 5 {
return None;
}
if !law.givens.iter().all(|g| g.type_name.trim() == "Fraction") {
return None;
}
let given_names: std::collections::BTreeSet<String> =
law.givens.iter().map(|g| g.name.clone()).collect();
if !matches!(law.rhs.node, Expr::Literal(crate::ast::Literal::Bool(true))) {
return None;
}
let Expr::FnCall(callee, claim_args) = &law.lhs.node else {
return None;
};
if claim_args.len() != 3 {
return None;
}
let subject_src = expr_dotted_name(callee)?;
let subject_short = subject_src
.rsplit('.')
.next()
.unwrap_or(&subject_src)
.to_string();
let d = shared::ident_name(&claim_args[0])?.to_string();
let v = shared::ident_name(&claim_args[1])?.to_string();
let e = shared::ident_name(&claim_args[2])?.to_string();
let subj_fd = find_fn_def_by_call_name(ctx, &subject_src)?;
if subj_fd.return_type != "Bool" || subj_fd.params.len() != 3 || !subj_fd.effects.is_empty() {
return None;
}
let [Stmt::Expr(body)] = subj_fd.body.stmts() else {
return None;
};
let lt_args = shared::call_named(body, "lessThan", 2)?;
let (mx, my) = match_abs_magnitude(<_args[0])?;
let p0 = &subj_fd.params[0].0;
let p1 = &subj_fd.params[1].0;
let p2 = &subj_fd.params[2].0;
if &mx != p0 || &my != p1 || shared::ident_name(<_args[1]) != Some(p2.as_str()) {
return None;
}
let when = law.when.as_ref()?;
let conj = shared::collect_when_clauses(when);
if conj.len() != 6 {
return None;
}
let subject_endpoint = |c: &Spanned<Expr>| -> Option<String> {
let (short, args) = shared::short_call_name_args(c)?;
if short != subject_short || args.len() != 3 {
return None;
}
if shared::ident_name(&args[1]) != Some(v.as_str())
|| shared::ident_name(&args[2]) != Some(e.as_str())
{
return None;
}
shared::ident_name(&args[0]).map(str::to_string)
};
let mut lo: Option<String> = None;
let mut hi: Option<String> = None;
let mut saw_v_nonneg = false;
let mut saw_guard = false;
for c in &conj {
if let Some(args) = shared::call_named(c, "isNonNeg", 1) {
if let Some(margs) = shared::call_named(&args[0], "minus", 2) {
let a0 = shared::ident_name(&margs[0]);
let a1 = shared::ident_name(&margs[1]);
if a0 == Some(d.as_str()) {
lo = a1.map(str::to_string).or(lo); } else if a1 == Some(d.as_str()) {
hi = a0.map(str::to_string).or(hi); } else {
return None;
}
continue;
}
if shared::ident_name(&args[0]) == Some(v.as_str()) {
saw_v_nonneg = true;
continue;
}
return None;
}
if let Expr::BinOp(crate::ast::BinOp::Neq, l, r) = &c.node {
if expr_dotted_name(l).as_deref() == Some(format!("{d}.bottom").as_str())
&& matches!(r.node, Expr::Literal(crate::ast::Literal::Int(0)))
{
saw_guard = true;
continue;
}
return None;
}
subject_endpoint(c)?;
}
let lo = lo?;
let hi = hi?;
let mut saw_lo_endpoint = false;
let mut saw_hi_endpoint = false;
for c in &conj {
if let Some(arg0) = subject_endpoint(c) {
if arg0 == lo {
saw_lo_endpoint = true;
} else if arg0 == hi {
saw_hi_endpoint = true;
}
}
}
if !(saw_v_nonneg && saw_guard && saw_lo_endpoint && saw_hi_endpoint) {
return None;
}
let roles: std::collections::BTreeSet<String> = [&d, &lo, &hi, &v, &e]
.iter()
.map(|s| (*s).clone())
.collect();
if roles.len() != 5 || roles != given_names {
return None;
}
Some(IntervalMono {
d,
lo,
hi,
v,
e,
subject: subject_src,
})
}
pub(in crate::codegen::lean) fn recognize_interval_monotonicity(
vb: &VerifyBlock,
law: &VerifyLaw,
ctx: &CodegenContext,
) -> bool {
recognize_interval_monotonicity_shape(vb, law, ctx).is_some()
}
pub(super) fn emit_interval_monotonicity_law(
vb: &VerifyBlock,
law: &VerifyLaw,
ctx: &CodegenContext,
theorem_base: &str,
) -> Option<AutoProof> {
let IntervalMono {
d,
lo,
hi,
v,
e,
subject,
} = recognize_interval_monotonicity_shape(vb, law, ctx)?;
let d = aver_name_to_lean(&d);
let lo = aver_name_to_lean(&lo);
let hi = aver_name_to_lean(&hi);
let v = aver_name_to_lean(&v);
let e = aver_name_to_lean(&e);
let subj = aver_name_to_lean(&subject);
let p = format!("{theorem_base}_im_");
let text = render_interval_mono(theorem_base, &p, &subj, &d, &lo, &hi, &v, &e);
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_interval_mono(
base: &str,
p: &str,
subj: &str,
d: &str,
lo: &str,
hi: &str,
v: &str,
e: &str,
) -> String {
let helpers = format!(
r#"set_option maxHeartbeats 1000000 in
theorem {p}absMulSelf (n : Int) : absInt n * absInt n = n * n := by
by_cases h : n < 0 <;> simp only [absInt, h, if_true, if_false] <;> grind
set_option maxHeartbeats 1000000 in
theorem {p}leAbsMul (m n : Int) : m * n ≤ absInt m * absInt n := by
by_cases h1 : m < 0 <;> by_cases h2 : n < 0 <;>
simp only [absInt, h1, h2, if_true, if_false] <;>
first
| grind
| (apply Int.mul_le_mul_of_nonneg_right <;> omega)
| (apply Int.mul_le_mul_of_nonneg_left <;> omega)
set_option maxHeartbeats 1000000 in
theorem {p}absExtract (x b : Fraction)
(h : lessThan (absFraction x) b = true) : lessThan x b = true := by
simp only [lessThan, absFraction, decide_eq_true_eq] at h ⊢
rw [{p}absMulSelf] at h
have key := Int.mul_le_mul_of_nonneg_right ({p}leAbsMul x.top x.bottom)
(aver_sq_nonneg b.bottom)
omega
set_option maxHeartbeats 1000000 in
theorem {p}absIntro (x b : Fraction)
(h1 : lessThan x b = true) (h2 : lessThan (negate x) b = true) :
lessThan (absFraction x) b = true := by
simp only [lessThan, absFraction, negate, decide_eq_true_eq] at h1 h2 ⊢
by_cases ht : x.top < 0 <;> by_cases hb2 : x.bottom < 0 <;>
simp only [absInt, ht, hb2, if_true, if_false] <;> grind
set_option maxHeartbeats 1000000 in
theorem {p}absNeg (x : Fraction) : absFraction (negate x) = absFraction x := by
simp only [absFraction, negate]
by_cases h : x.top < 0 <;> simp only [absInt, h, if_true, if_false] <;>
(congr 1 <;> grind)
set_option maxHeartbeats 1000000 in
theorem {p}leLtTrans (a b c : Fraction)
(hab : isNonNeg (minus b a) = true) (hbc : lessThan b c = true)
(ha : a.bottom ≠ 0) (hb : b.bottom ≠ 0) : lessThan a c = true := by
simp only [isNonNeg, minus, lessThan, decide_eq_true_eq, ge_iff_le] at hab hbc ⊢
have hq2 : 0 < a.bottom * a.bottom := by
rcases (by omega : a.bottom < 0 ∨ 0 < a.bottom) with h | h
· have : 0 < (-a.bottom) * (-a.bottom) := Int.mul_pos (by omega) (by omega)
rwa [Int.neg_mul_neg] at this
· exact Int.mul_pos h h
have hs2 : 0 < b.bottom * b.bottom := by
rcases (by omega : b.bottom < 0 ∨ 0 < b.bottom) with h | h
· have : 0 < (-b.bottom) * (-b.bottom) := Int.mul_pos (by omega) (by omega)
rwa [Int.neg_mul_neg] at this
· exact Int.mul_pos h h
have hu2 : 0 ≤ c.bottom * c.bottom := aver_sq_nonneg _
have step1 : (b.top * b.bottom * (c.bottom * c.bottom)) * (a.bottom * a.bottom)
< (c.top * c.bottom * (b.bottom * b.bottom)) * (a.bottom * a.bottom) :=
Int.mul_lt_mul_of_pos_right hbc hq2
have hab' : a.top * (b.bottom * b.bottom) * a.bottom
≤ b.top * b.bottom * (a.bottom * a.bottom) := by grind
have step2 : (a.top * (b.bottom * b.bottom) * a.bottom) * (c.bottom * c.bottom)
≤ (b.top * b.bottom * (a.bottom * a.bottom)) * (c.bottom * c.bottom) :=
Int.mul_le_mul_of_nonneg_right hab' hu2
have hmul : (a.top * a.bottom * (c.bottom * c.bottom)) * (b.bottom * b.bottom)
< (c.top * c.bottom * (a.bottom * a.bottom)) * (b.bottom * b.bottom) := by grind
exact Int.lt_of_mul_lt_mul_right hmul (by omega)
set_option maxHeartbeats 1000000 in
theorem {p}monoR (a b w : Fraction)
(hab : isNonNeg (minus b a) = true) (hw : isNonNeg w = true) :
isNonNeg (minus (minus (times b w) oneFraction) (minus (times a w) oneFraction)) = true := by
simp only [isNonNeg, minus, times, oneFraction, decide_eq_true_eq, ge_iff_le] at hab hw ⊢
have hp : (0:Int) ≤ (b.top * a.bottom - a.top * b.bottom) * (b.bottom * a.bottom)
* ((w.top * w.bottom) * (w.bottom * w.bottom)) := by aver_int_order
grind
set_option maxHeartbeats 1000000 in
theorem {p}monoRneg (a b w : Fraction)
(hab : isNonNeg (minus b a) = true) (hw : isNonNeg w = true) :
isNonNeg (minus (negate (minus (times a w) oneFraction))
(negate (minus (times b w) oneFraction))) = true := by
simp only [isNonNeg, minus, times, negate, oneFraction, decide_eq_true_eq, ge_iff_le] at hab hw ⊢
have hp : (0:Int) ≤ (b.top * a.bottom - a.top * b.bottom) * (b.bottom * a.bottom)
* ((w.top * w.bottom) * (w.bottom * w.bottom)) := by aver_int_order
grind
set_option maxHeartbeats 1000000 in
theorem {p}denoms (x b : Fraction) (h : lessThan x b = true) :
x.bottom ≠ 0 ∧ b.bottom ≠ 0 := by
simp only [lessThan, decide_eq_true_eq] at h
constructor <;> intro hc <;> rw [hc] at h <;> simp at h"#
);
let assembly = format!(
r#"set_option maxHeartbeats 1000000 in
theorem {base} : ∀ ({d} {lo} {hi} {v} {e} : Fraction),
isNonNeg (minus {d} {lo}) = true →
isNonNeg (minus {hi} {d}) = true →
isNonNeg {v} = true →
{d}.bottom ≠ 0 →
{subj} {lo} {v} {e} = true →
{subj} {hi} {v} {e} = true →
{subj} {d} {v} {e} = true := by
intro {d} {lo} {hi} {v} {e} h1 h2 h3 h4 h5 h6
first
| (simp only [{subj}] at h5 h6 ⊢
obtain ⟨hPlo_b, _⟩ := {p}denoms _ _ h5
obtain ⟨hPhi_b, _⟩ := {p}denoms _ _ h6
have hPhi_raw : (minus (times {hi} {v}) oneFraction).bottom ≠ 0 := by
simp only [absFraction] at hPhi_b
intro hc; apply hPhi_b; rw [hc]; simp [absInt]
have hPlo_raw : (minus (times {lo} {v}) oneFraction).bottom ≠ 0 := by
simp only [absFraction] at hPlo_b
intro hc; apply hPlo_b; rw [hc]; simp [absInt]
have hvb : {v}.bottom ≠ 0 := by
simp only [minus, times, oneFraction] at hPhi_raw
intro hc; apply hPhi_raw; rw [hc]; simp
have hP_raw : (minus (times {d} {v}) oneFraction).bottom ≠ 0 := by
simp only [minus, times, oneFraction]
exact Int.mul_ne_zero (Int.mul_ne_zero h4 hvb) (by decide)
apply {p}absIntro
· have hPhi_lt : lessThan (minus (times {hi} {v}) oneFraction) {e} = true :=
{p}absExtract _ _ h6
have hmono : isNonNeg (minus (minus (times {hi} {v}) oneFraction)
(minus (times {d} {v}) oneFraction)) = true :=
{p}monoR {d} {hi} {v} h2 h3
exact {p}leLtTrans _ _ _ hmono hPhi_lt hP_raw hPhi_raw
· have h5' : lessThan (absFraction (negate (minus (times {lo} {v}) oneFraction))) {e} = true := by
rw [{p}absNeg]; exact h5
have hnegPlo_lt : lessThan (negate (minus (times {lo} {v}) oneFraction)) {e} = true :=
{p}absExtract _ _ h5'
have hmono2 : isNonNeg (minus (negate (minus (times {lo} {v}) oneFraction))
(negate (minus (times {d} {v}) oneFraction))) = true :=
{p}monoRneg {lo} {d} {v} h1 h3
have hnegP_b : (negate (minus (times {d} {v}) oneFraction)).bottom ≠ 0 := by
simp only [negate]; exact hP_raw
have hnegPlo_b : (negate (minus (times {lo} {v}) oneFraction)).bottom ≠ 0 := by
simp only [negate]; exact hPlo_raw
exact {p}leLtTrans _ _ _ hmono2 hnegPlo_lt hnegP_b hnegPlo_b)
| sorry"#
);
format!("{helpers}\n{assembly}")
}