use crate::deriv::log::{DerivedExpr, RewriteStep, SideCondition};
use crate::kernel::{ExprData, ExprId, ExprPool};
fn rule_to_tactic(rule_name: &str) -> &'static str {
match rule_name {
"const_fold" => "by ring",
"add_zero" => "by simp [add_zero]",
"mul_one" => "by simp [mul_one]",
"mul_zero" => "by simp [mul_zero]",
"pow_one" => "by simp [pow_one]",
"pow_zero" => "by simp [pow_zero]",
"sin_neg" => "by simp [Real.sin_neg]",
"cos_neg" => "by simp [Real.cos_neg]",
"log_of_exp" => "by simp [Real.log_exp]",
"exp_of_log" => "by sorry",
"log_of_product" | "log_of_product_positive" => "by sorry",
"sum_of_logs" => "by sorry",
"log_of_quotient" => "by sorry",
"product_of_exps" => "by simp only [← Real.exp_add]",
"log_of_pow" => "by simp [Real.log_pow]",
"sin_sq_plus_cos_sq" => "by rw [Real.sin_sq_add_cos_sq]",
"power_rule" | "constant_rule" | "sum_rule" | "constant_multiple_rule" => "by ring",
"int_sin" | "int_cos" | "int_exp" | "log_rule" => "by sorry",
"collect_add_terms" | "collect_mul_factors" => "by ring",
"flatten_mul" | "flatten_add" | "canonical_order" => "by ring",
"expand_mul" => "by ring",
"tan_expand" => "by rw [Real.tan_eq_sin_div_cos, div_eq_mul_inv]",
_ => "by ring_nf; simp",
}
}
fn is_diff_certificate(wrt: Option<ExprId>) -> bool {
wrt.is_some()
}
fn is_differentiation_rule(rule_name: &str) -> bool {
rule_name.starts_with("diff_")
|| matches!(
rule_name,
"sum_rule"
| "product_rule"
| "quotient_rule"
| "chain_rule"
| "power_rule"
| "power_rule_n0"
| "power_rule_n1"
)
}
fn is_integration_rule(rule_name: &str) -> bool {
rule_name.starts_with("int_")
|| rule_name.starts_with("risch_")
|| matches!(
rule_name,
"fundamental_theorem_of_calculus"
| "log_rule"
| "gosper_indefinite"
| "gosper_definite_telescope"
)
}
fn is_unary_of_var(before: ExprId, wrt: ExprId, pool: &ExprPool) -> bool {
pool.with(
before,
|d| matches!(d, ExprData::Func { args, .. } if args.len() == 1 && args[0] == wrt),
)
}
fn is_pow_of_var(before: ExprId, wrt: ExprId, pool: &ExprPool) -> bool {
pool.with(
before,
|d| matches!(d, ExprData::Pow { base, .. } if *base == wrt),
)
}
fn composite_pow_inner_exp(before: ExprId, wrt: ExprId, pool: &ExprPool) -> Option<i64> {
pool.with(before, |d| {
let arg = match d {
ExprData::Func { args, .. } if args.len() == 1 => args[0],
_ => return None,
};
pool.with(arg, |inner| match inner {
ExprData::Pow { base, exp } if *base == wrt => pool.with(*exp, |e| match e {
ExprData::Integer(n) => n.0.to_i64().filter(|&k| k >= 2),
_ => None,
}),
_ => None,
})
})
}
fn chain_outer_lemma(rule_name: &str) -> Option<&'static str> {
match rule_name {
"diff_sin" => Some("sin"),
"diff_cos" => Some("cos"),
"diff_exp" => Some("exp"),
_ => None,
}
}
fn wrt_name(wrt: ExprId, pool: &ExprPool) -> String {
pool.with(wrt, |d| match d {
ExprData::Symbol { name, .. } => name.clone(),
_ => "x".to_string(),
})
}
fn pointwise_hasderivat_lemma(name: &str) -> Option<&'static str> {
match name {
"sin" => Some("Real.hasDerivAt_sin"),
"cos" => Some("Real.hasDerivAt_cos"),
"exp" => Some("Real.hasDerivAt_exp"),
_ => None,
}
}
fn unary_func_name(expr: ExprId, wrt: ExprId, pool: &ExprPool) -> Option<String> {
pool.with(expr, |d| match d {
ExprData::Func { name, args } if args.len() == 1 && args[0] == wrt => Some(name.clone()),
_ => None,
})
}
fn power_chain_certificate(
before: ExprId,
wrt: ExprId,
pool: &ExprPool,
) -> Option<(Option<String>, String)> {
let (base, exp_n) = pool.with(before, |d| match d {
ExprData::Pow { base, exp } => pool.with(*exp, |e| match e {
ExprData::Integer(n) => n.0.to_i64().map(|k| (*base, k)),
_ => None,
}),
_ => None,
})?;
let name = unary_func_name(base, wrt, pool)?;
let lemma = pointwise_hasderivat_lemma(&name)?;
let var = wrt_name(wrt, pool);
if exp_n >= 2 {
let tactic = format!(
"by\n \
have hf := {lemma} {var}\n \
rw [(hf.pow {exp_n}).deriv]\n \
push_cast\n \
ring"
);
return Some((None, tactic));
}
if exp_n == -1 {
let binder = format!("({var} : ℝ) (hne : Real.{name} {var} ≠ 0)");
let tactic = format!(
"by\n \
have hf := {lemma} {var}\n \
rw [(hf.inv hne).deriv]\n \
ring"
);
return Some((Some(binder), tactic));
}
None
}
fn quotient_chain_certificate(
before: ExprId,
wrt: ExprId,
pool: &ExprPool,
) -> Option<(Option<String>, String)> {
let factor_inv_base = |id: ExprId| -> Option<ExprId> {
pool.with(id, |d| match d {
ExprData::Pow { base, exp } => pool
.with(*exp, |e| matches!(e, ExprData::Integer(n) if n.0 == -1))
.then_some(*base),
_ => None,
})
};
let (num, den_base) = pool.with(before, |d| match d {
ExprData::Mul(xs) if xs.len() == 2 => {
if let Some(b) = factor_inv_base(xs[1]) {
Some((xs[0], b))
} else {
factor_inv_base(xs[0]).map(|b| (xs[1], b))
}
}
_ => None,
})?;
let fname = unary_func_name(num, wrt, pool)?;
let gname = unary_func_name(den_base, wrt, pool)?;
let flemma = pointwise_hasderivat_lemma(&fname)?;
let glemma = pointwise_hasderivat_lemma(&gname)?;
let var = wrt_name(wrt, pool);
let binder = format!("({var} : ℝ) (hne : Real.{gname} {var} ≠ 0)");
let tactic = format!(
"by\n \
have hf := {flemma} {var}\n \
have hg := ({glemma} {var}).inv hne\n \
rw [(hf.mul hg).deriv]\n \
field_simp [hne]"
);
Some((Some(binder), tactic))
}
fn diff_sqrt_certificate(wrt: ExprId, pool: &ExprPool) -> (Option<String>, String) {
let var = wrt_name(wrt, pool);
let binder = format!("({var} : ℝ) (hx : 0 < {var})");
let tactic = "by\n \
have h := (Real.hasDerivAt_sqrt hx.ne').deriv\n \
rw [h]\n \
ring"
.to_string();
(Some(binder), tactic)
}
fn registry_diff_certificate(
before: ExprId,
wrt: ExprId,
pool: &ExprPool,
) -> Option<(Option<String>, String)> {
let name = unary_func_name(before, wrt, pool)?;
match name.as_str() {
"tan" => {
let var = wrt_name(wrt, pool);
let binder = format!("({var} : ℝ) (hne : Real.cos {var} ≠ 0)");
let tactic = format!(
"by\n \
have hderiv := (Real.hasDerivAt_tan hne).deriv\n \
have hinv : (1 + Real.tan {var} ^ 2)⁻¹ = Real.cos {var} ^ 2 := \
Real.inv_one_add_tan_sq hne\n \
have hsq : 1 + Real.tan {var} ^ 2 = (Real.cos {var} ^ 2)⁻¹ := by \
rw [← hinv, inv_inv]\n \
rw [hderiv, one_div, hsq]\n \
ring"
);
Some((Some(binder), tactic))
}
_ => None,
}
}
fn chain_diff_tactic(
rule_name: &str,
before: ExprId,
wrt: ExprId,
pool: &ExprPool,
) -> Option<String> {
let n = composite_pow_inner_exp(before, wrt, pool)?;
let suffix = chain_outer_lemma(rule_name)?;
let var_name = wrt_name(wrt, pool);
Some(format!(
"by\n \
have hg := hasDerivAt_pow {n} {var_name}\n \
rw [(hg.{suffix}).deriv]\n \
push_cast\n \
ring"
))
}
const UNCONDITIONAL_DIFF_TACTIC: &str = "by\n \
simp (config := { maxDischargeDepth := 8 }) only [deriv_add, deriv_mul, deriv_pow, \
deriv_const, deriv_id'', Real.deriv_sin, Real.deriv_cos, Real.deriv_exp, \
differentiableAt_pow, differentiableAt_id', differentiableAt_const, \
Real.differentiableAt_sin, Real.differentiableAt_cos, Real.differentiableAt_exp, \
DifferentiableAt.add, DifferentiableAt.mul, DifferentiableAt.pow]\n \
try ring";
fn diff_body_unconditional(before: ExprId, wrt: ExprId, pool: &ExprPool) -> bool {
fn walk(f: ExprId, wrt: ExprId, pool: &ExprPool) -> bool {
pool.with(f, |d| match d {
ExprData::Integer(_) | ExprData::Rational(_) | ExprData::Float(_) => true,
ExprData::Symbol { .. } => true,
ExprData::Pow { base, exp } => {
*base == wrt
&& pool.with(*exp, |e| match e {
ExprData::Integer(n) => n.0.to_i64().is_some_and(|k| k >= 0),
_ => false,
})
}
ExprData::Func { name, args } => {
matches!(name.as_str(), "sin" | "cos" | "exp") && args.len() == 1 && args[0] == wrt
}
ExprData::Add(xs) => xs.iter().all(|&c| walk(c, wrt, pool)),
ExprData::Mul(xs) => xs.iter().all(|&c| walk(c, wrt, pool)),
_ => false,
})
}
walk(before, wrt, pool)
}
fn diff_rule_to_tactic(rule_name: &str) -> Option<&'static str> {
match rule_name {
"diff_identity" => Some("by simp [deriv_id]"),
"diff_const" => Some("by simp [deriv_const]"),
"diff_univariate_poly" => Some(UNCONDITIONAL_DIFF_TACTIC),
"sum_rule" => Some(UNCONDITIONAL_DIFF_TACTIC),
"product_rule" => Some(UNCONDITIONAL_DIFF_TACTIC),
"power_rule" | "power_rule_n1" => Some("by simp [deriv_pow, deriv_mul]; try ring"),
"power_rule_n0" => Some("by simp [deriv_const]"),
"diff_sin" => Some("by simp [Real.deriv_sin, one_mul, mul_one]"),
"diff_cos" => Some("by simp [Real.deriv_cos, one_mul, mul_one]"),
"diff_exp" => Some("by simp [Real.deriv_exp, one_mul, mul_one]"),
"diff_log" => Some("by simp [Real.deriv_log, one_mul, mul_one]"),
"diff_sqrt" => None,
"diff_forward" | "diff_primitive_registry" | "diff_piecewise" | "diff_root_sum" => None,
_ => None,
}
}
fn diff_step_certificate(
step: &RewriteStep,
wrt: ExprId,
pool: &ExprPool,
) -> Option<(Option<String>, String)> {
match step.rule_name {
"diff_sin" | "diff_cos" | "diff_exp" => {
if is_unary_of_var(step.before, wrt, pool) {
return diff_rule_to_tactic(step.rule_name).map(|t| (None, t.to_string()));
}
chain_diff_tactic(step.rule_name, step.before, wrt, pool).map(|t| (None, t))
}
"diff_log" => {
if is_unary_of_var(step.before, wrt, pool) {
diff_rule_to_tactic("diff_log").map(|t| (None, t.to_string()))
} else {
None
}
}
"diff_sqrt" => {
if is_unary_of_var(step.before, wrt, pool) {
Some(diff_sqrt_certificate(wrt, pool))
} else {
None
}
}
"diff_primitive_registry" => registry_diff_certificate(step.before, wrt, pool),
"power_rule" | "power_rule_n1" | "power_rule_n0" => {
if is_pow_of_var(step.before, wrt, pool)
&& diff_body_unconditional(step.before, wrt, pool)
{
diff_rule_to_tactic(step.rule_name).map(|t| (None, t.to_string()))
} else if step.rule_name == "power_rule" {
power_chain_certificate(step.before, wrt, pool)
} else {
None
}
}
"diff_univariate_poly" | "sum_rule" => diff_body_unconditional(step.before, wrt, pool)
.then(|| (None, UNCONDITIONAL_DIFF_TACTIC.to_string())),
"product_rule" => quotient_chain_certificate(step.before, wrt, pool).or_else(|| {
diff_body_unconditional(step.before, wrt, pool)
.then(|| (None, UNCONDITIONAL_DIFF_TACTIC.to_string()))
}),
name => diff_rule_to_tactic(name).map(|t| (None, t.to_string())),
}
}
fn symbol_name(id: ExprId, pool: &ExprPool) -> Option<String> {
pool.with(id, |d| match d {
ExprData::Symbol { name, .. } => Some(name.clone()),
_ => None,
})
}
fn positivity_tactic(rule_name: &str, names: &[String]) -> Option<String> {
match (rule_name, names) {
("exp_of_log", [x]) => Some(format!("by rw [Real.exp_log h{x}]")),
("log_of_product" | "log_of_product_positive" | "sum_of_logs", [x, y]) => Some(format!(
"by rw [Real.log_mul (ne_of_gt h{x}) (ne_of_gt h{y})]"
)),
("log_of_quotient", [x, y]) => Some(format!(
"by rw [Real.log_mul (ne_of_gt h{x}) (inv_ne_zero (ne_of_gt h{y})), \
Real.log_inv]; ring"
)),
_ => None,
}
}
fn positivity_certificate(step: &RewriteStep, pool: &ExprPool) -> Option<(String, String)> {
if step.side_conditions.is_empty() {
return None;
}
let names: Vec<String> = step
.side_conditions
.iter()
.map(|c| match c {
SideCondition::Positive(id) => symbol_name(*id, pool),
_ => None,
})
.collect::<Option<Vec<_>>>()?;
let tactic = positivity_tactic(step.rule_name, &names)?;
let mut binders = names
.iter()
.map(|n| format!("({n} : ℝ)"))
.collect::<Vec<_>>();
binders.extend(names.iter().map(|n| format!("(h{n} : 0 < {n})")));
Some((binders.join(" "), tactic))
}
fn inv_cancel_base(before: ExprId, after: ExprId, pool: &ExprPool) -> Option<(ExprId, i64)> {
let (a, b) = pool.with(before, |d| match d {
ExprData::Mul(xs) if xs.len() == 2 => Some((xs[0], xs[1])),
_ => None,
})?;
let base_exp = |id: ExprId| -> (ExprId, i64) {
pool.with(id, |d| match d {
ExprData::Pow { base, exp } => pool
.with(*exp, |e| match e {
ExprData::Integer(n) => n.0.to_i64(),
_ => None,
})
.map(|n| (*base, n))
.unwrap_or((id, 1)),
_ => (id, 1),
})
};
let (base_a, exp_a) = base_exp(a);
let (base_b, exp_b) = base_exp(b);
if base_a != base_b || (exp_a >= 0 && exp_b >= 0) {
return None;
}
let net = exp_a + exp_b;
let expected_after = if net == 0 {
pool.integer(1_i32)
} else {
pool.pow(base_a, pool.integer(net))
};
(after == expected_after).then_some((base_a, net))
}
fn inv_cancel_certificate(
before: ExprId,
after: ExprId,
pool: &ExprPool,
) -> Option<(String, String)> {
let (base, net) = inv_cancel_base(before, after, pool)?;
let tactic = if net == 0 {
"by field_simp [hne]".to_string()
} else {
"by\n field_simp [hne]\n ring".to_string()
};
if let Some(sym) = symbol_name(base, pool) {
let binder = format!("({sym} : ℝ) (hne : {sym} ≠ 0)");
return Some((binder, tactic));
}
let name = pool.with(base, |d| match d {
ExprData::Func { name, args } if args.len() == 1 => {
symbol_name(args[0], pool).map(|sym| (name.clone(), sym))
}
_ => None,
})?;
let (name, sym) = name;
if !matches!(name.as_str(), "sin" | "cos" | "exp") {
return None;
}
let binder = format!("({sym} : ℝ) (hne : Real.{name} {sym} ≠ 0)");
Some((binder, tactic))
}
fn is_double_inv_cancel(before: ExprId, after: ExprId, pool: &ExprPool) -> bool {
let base = pool.with(before, |d| match d {
ExprData::Pow { base, exp }
if pool.with(*exp, |e| matches!(e, ExprData::Integer(n) if n.0 == -1)) =>
{
pool.with(*base, |bd| match bd {
ExprData::Pow { base: b2, exp: e2 }
if pool.with(*e2, |e| matches!(e, ExprData::Integer(n) if n.0 == -1)) =>
{
Some(*b2)
}
_ => None,
})
}
_ => None,
});
match base {
Some(a) => after == a || after == pool.pow(a, pool.integer(1_i32)),
None => false,
}
}
fn sin_double_angle_certificate(before: ExprId, pool: &ExprPool) -> Option<String> {
let factors = pool.with(before, |d| match d {
ExprData::Mul(xs) => Some(xs.clone()),
_ => None,
})?;
let arg = factors.iter().find_map(|&fac| {
pool.with(fac, |d| match d {
ExprData::Func { name, args } if name == "sin" && args.len() == 1 => Some(args[0]),
_ => None,
})
})?;
let arg_str = expr_to_lean(arg, pool);
Some(format!(
"by rw [mul_comm ({arg_str} : ℝ) (2 : ℝ), Real.sin_two_mul]; ring"
))
}
fn plain_step_certificate(step: &RewriteStep, pool: &ExprPool) -> Option<(Option<String>, String)> {
if step.rule_name == "sin_double_angle" {
return sin_double_angle_certificate(step.before, pool).map(|t| (None, t));
}
if inv_cancel_base(step.before, step.after, pool).is_some() {
return inv_cancel_certificate(step.before, step.after, pool).map(|(b, t)| (Some(b), t));
}
if is_double_inv_cancel(step.before, step.after, pool) {
return Some((None, "by simp [inv_inv]".to_string()));
}
if let Some((binders, tactic)) = positivity_certificate(step, pool) {
return Some((Some(binders), tactic));
}
let tactic = rule_to_tactic(step.rule_name);
if tactic.contains("sorry") {
None
} else {
Some((None, tactic.to_string()))
}
}
fn step_is_certifiable(step: &RewriteStep, wrt: Option<ExprId>, pool: &ExprPool) -> bool {
if is_integration_rule(step.rule_name) {
return false;
}
if let Some(var) = wrt {
if is_differentiation_rule(step.rule_name) {
return diff_step_certificate(step, var, pool).is_some();
}
return plain_step_certificate(step, pool).is_some();
}
plain_step_certificate(step, pool).is_some()
}
pub fn emit_header() -> String {
"import Mathlib.Tactic\n\
import Mathlib.Analysis.SpecialFunctions.Trigonometric.Basic\n\
import Mathlib.Analysis.SpecialFunctions.Log.Basic\n\
import Mathlib.Analysis.SpecialFunctions.Gamma.Basic\n\
\n\
open Real\n\n"
.to_string()
}
pub fn emit_diff_header() -> String {
"import Mathlib.Tactic\n\
import Mathlib.Analysis.Calculus.Deriv.Basic\n\
import Mathlib.Analysis.Calculus.Deriv.Pow\n\
import Mathlib.Analysis.Calculus.Deriv.Mul\n\
import Mathlib.Analysis.Calculus.Deriv.Inv\n\
import Mathlib.Analysis.SpecialFunctions.Trigonometric.Deriv\n\
import Mathlib.Analysis.SpecialFunctions.Trigonometric.ArctanDeriv\n\
import Mathlib.Analysis.SpecialFunctions.ExpDeriv\n\
import Mathlib.Analysis.SpecialFunctions.Log.Deriv\n\
import Mathlib.Analysis.SpecialFunctions.Sqrt\n\
\n\
open Real\n\n"
.to_string()
}
pub fn emit_limit_header() -> String {
"import Mathlib.Tactic\n\
import Mathlib.Analysis.SpecialFunctions.ExpDeriv\n\
import Mathlib.Analysis.SpecialFunctions.Pow.Real\n\
import Mathlib.Topology.Algebra.Order.LiminfLimsup\n\
\n\
open Real Filter Topology\n\n"
.to_string()
}
pub fn emit_tendsto_cert(expr: ExprId, var: ExprId, lim: ExprId, pool: &ExprPool) -> String {
let var_name = pool.with(var, |d| match d {
ExprData::Symbol { name, .. } => name.clone(),
_ => "x".to_string(),
});
let body = expr_to_lean(expr, pool);
let (codom_filter, limit_display) = lean_codom_filter(lim, pool);
let tactic = tendsto_tactic(expr, var, lim, pool);
if tactic.contains("sorry") || tactic.contains("admit") {
return String::new();
}
let mut out = emit_limit_header();
out.push_str(&format!(
"-- Filter.Tendsto certificate: lim_{{x→+∞}} f(x) = {limit_display}\n"
));
out.push_str(&format!(
"example : Filter.Tendsto (fun ({var_name} : ℝ) => {body}) Filter.atTop {codom_filter} :=\n"
));
out.push_str(&format!(" {tactic}\n"));
out
}
fn lean_codom_filter(lim: ExprId, pool: &ExprPool) -> (String, String) {
let is_inf = pool.with(
lim,
|d| matches!(d, ExprData::Symbol { name, .. } if name == "∞"),
);
if is_inf {
return ("Filter.atTop".to_string(), "+∞".to_string());
}
let val_str = pool.with(lim, |d| match d {
ExprData::Integer(n) if n.0 == 0 => "(0 : ℝ)".to_string(),
ExprData::Integer(n) if n.0 == 1 => "(1 : ℝ)".to_string(),
_ => expr_to_lean(lim, pool),
});
(format!("(nhds {val_str})"), val_str)
}
fn tendsto_tactic(expr: ExprId, var: ExprId, lim: ExprId, pool: &ExprPool) -> String {
let is_zero = pool.with(lim, |d| match d {
ExprData::Integer(n) => n.0 == 0,
_ => false,
});
let is_pos_inf = pool.with(lim, |d| match d {
ExprData::Symbol { name, .. } => name == "∞",
_ => false,
});
if is_zero && matches_exp_neg_var(expr, var, pool) {
return "tendsto_exp_neg_atTop_nhds_zero".to_string();
}
if is_zero && matches_pow_mul_exp_neg(expr, var, pool) {
return "by\n have := tendsto_pow_mul_exp_neg_atTop_nhds_zero\n exact this"
.to_string();
}
if is_pos_inf && matches_exp_var(expr, var, pool) {
return "tendsto_exp_atTop".to_string();
}
if is_zero && matches_exp_ratio_to_zero(expr, var, pool) {
return "by\n simp only [div_eq_mul_inv, ← Real.exp_neg]\n exact tendsto_exp_neg_atTop_nhds_zero.comp tendsto_id".to_string();
}
"by sorry".to_string()
}
fn matches_exp_neg_var(expr: ExprId, var: ExprId, pool: &ExprPool) -> bool {
pool.with(expr, |d| {
if let ExprData::Func { name, args } = d {
if name == "exp" && args.len() == 1 {
let arg = args[0];
return pool.with(arg, |d2| {
if let ExprData::Mul(xs) = d2 {
xs.len() == 2
&& xs.contains(&var)
&& xs.iter().any(|&x| {
pool.with(x, |d3| matches!(d3, ExprData::Integer(n) if n.0 == -1))
})
} else {
false
}
});
}
}
false
})
}
fn matches_pow_mul_exp_neg(expr: ExprId, var: ExprId, pool: &ExprPool) -> bool {
pool.with(expr, |d| {
if let ExprData::Mul(xs) = d {
let has_pow = xs.iter().any(|&x| {
pool.with(
x,
|d2| matches!(d2, ExprData::Pow { base, .. } if *base == var),
)
});
let has_exp_neg = xs.iter().any(|&x| matches_exp_neg_var(x, var, pool));
has_pow && has_exp_neg
} else {
false
}
})
}
fn matches_exp_var(expr: ExprId, var: ExprId, pool: &ExprPool) -> bool {
pool.with(expr, |d| {
if let ExprData::Func { name, args } = d {
name == "exp" && args.len() == 1 && args[0] == var
} else {
false
}
})
}
fn matches_exp_ratio_to_zero(expr: ExprId, _var: ExprId, pool: &ExprPool) -> bool {
pool.with(expr, |d| {
if let ExprData::Mul(xs) = d {
let exp_count = xs
.iter()
.filter(|&&x| {
pool.with(
x,
|d2| matches!(d2, ExprData::Func { name, .. } if name == "exp"),
)
})
.count();
exp_count >= 2
} else {
false
}
})
}
pub fn emit_goal(before: ExprId, after: ExprId, pool: &ExprPool) -> String {
let before_str = expr_to_lean(before, pool);
let after_str = expr_to_lean(after, pool);
format!("example : {before_str} = {after_str}")
}
fn depends_on(haystack: ExprId, needle: ExprId, pool: &ExprPool) -> bool {
if haystack == needle {
return true;
}
pool.with(haystack, |d| match d {
ExprData::Add(xs) | ExprData::Mul(xs) | ExprData::Func { args: xs, .. } => {
xs.iter().any(|&c| depends_on(c, needle, pool))
}
ExprData::Pow { base, exp } => {
depends_on(*base, needle, pool) || depends_on(*exp, needle, pool)
}
ExprData::Predicate { args, .. } => args.iter().any(|&c| depends_on(c, needle, pool)),
ExprData::Piecewise { branches, default } => {
branches
.iter()
.any(|&(c, v)| depends_on(c, needle, pool) || depends_on(v, needle, pool))
|| depends_on(*default, needle, pool)
}
ExprData::BigO(a) => depends_on(*a, needle, pool),
ExprData::Forall { var, body }
| ExprData::Exists { var, body }
| ExprData::RootSum { var, body, .. } => {
depends_on(*var, needle, pool) || depends_on(*body, needle, pool)
}
_ => false,
})
}
fn diff_goal_body(before: ExprId, after: ExprId, wrt: ExprId, pool: &ExprPool) -> String {
let var_name = wrt_name(wrt, pool);
let binder = if depends_on(before, wrt, pool) {
var_name.clone()
} else {
format!("_{var_name}")
};
let before_str = expr_to_lean(before, pool);
let after_str = expr_to_lean(after, pool);
format!("deriv (fun ({binder} : ℝ) => {before_str}) {var_name} = {after_str}")
}
pub fn emit_diff_goal(before: ExprId, after: ExprId, wrt: ExprId, pool: &ExprPool) -> String {
format!("example : {}", diff_goal_body(before, after, wrt, pool))
}
pub fn emit_step(step: &RewriteStep, pool: &ExprPool) -> String {
emit_step_wrt(step, pool, None)
}
fn append_side_conditions(out: &mut String, step: &RewriteStep, pool: &ExprPool) {
if step.side_conditions.is_empty() {
return;
}
out.push_str("\n -- Side conditions: ");
let conds: Vec<String> = step
.side_conditions
.iter()
.map(|c| c.display_with(pool).to_string())
.collect();
out.push_str(&conds.join(", "));
}
pub fn emit_step_wrt(step: &RewriteStep, pool: &ExprPool, wrt: Option<ExprId>) -> String {
let diff_step = wrt.is_some() && is_differentiation_rule(step.rule_name);
if let (true, Some(var)) = (diff_step, wrt) {
let (binders, tactic) =
diff_step_certificate(step, var, pool).unwrap_or((None, "by sorry".to_string()));
let body = diff_goal_body(step.before, step.after, var, pool);
let mut out = match binders {
Some(b) => format!("example {b} : {body} :=\n {tactic}"),
None => format!("example : {body} :=\n {tactic}"),
};
append_side_conditions(&mut out, step, pool);
return out;
}
let (binders, tactic) =
plain_step_certificate(step, pool).unwrap_or((None, "by sorry".to_string()));
let before_str = expr_to_lean(step.before, pool);
let after_str = expr_to_lean(step.after, pool);
let mut out = match binders {
Some(b) => format!("example {b} : {before_str} = {after_str} :=\n {tactic}"),
None => format!("example : {before_str} = {after_str} :=\n {tactic}"),
};
append_side_conditions(&mut out, step, pool);
out
}
pub fn emit_lean_expr(derived: &DerivedExpr<ExprId>, pool: &ExprPool) -> String {
emit_lean_expr_wrt(derived, pool, None)
}
pub fn emit_lean_expr_wrt(
derived: &DerivedExpr<ExprId>,
pool: &ExprPool,
wrt: Option<ExprId>,
) -> String {
let steps = derived.log.steps();
if steps.is_empty() {
let diff_mode = is_diff_certificate(wrt);
let mut out = if diff_mode {
emit_diff_header()
} else {
emit_header()
};
let e = derived.value;
let lean_e = expr_to_lean(e, pool);
out.push_str(&format!(
"-- No rewrite steps recorded.\nexample : {lean_e} = {lean_e} :=\n rfl\n"
));
return out;
}
if steps.iter().any(|s| !step_is_certifiable(s, wrt, pool)) {
return String::new();
}
let diff_mode = is_diff_certificate(wrt);
let mut out = if diff_mode {
emit_diff_header()
} else {
emit_header()
};
for (i, step) in steps.iter().enumerate() {
out.push_str(&format!("-- Step {}: {}\n", i + 1, step.rule_name));
out.push_str(&emit_step_wrt(step, pool, wrt));
out.push_str("\n\n");
}
if out.contains("sorry") || out.contains("admit") {
return String::new();
}
out
}
fn antiderivative_in_certifiable_fragment(f: ExprId, var: ExprId, pool: &ExprPool) -> bool {
fn walk(f: ExprId, var: ExprId, pool: &ExprPool, in_product: bool) -> bool {
pool.with(f, |d| match d {
ExprData::Integer(_) | ExprData::Rational(_) | ExprData::Float(_) => true,
ExprData::Symbol { .. } => true,
ExprData::Pow { base, exp } => {
*base == var && pool.with(*exp, |e| matches!(e, ExprData::Integer(_)))
}
ExprData::Func { name, args } => {
matches!(name.as_str(), "sin" | "cos" | "exp") && args.len() == 1 && args[0] == var
}
ExprData::Add(xs) => {
!in_product && xs.iter().all(|&c| walk(c, var, pool, false))
}
ExprData::Mul(xs) => xs.iter().all(|&c| walk(c, var, pool, true)),
_ => false,
})
}
walk(f, var, pool, false)
}
pub fn emit_integration_cert(
antiderivative: ExprId,
integrand: ExprId,
var: ExprId,
pool: &ExprPool,
) -> String {
if !antiderivative_in_certifiable_fragment(antiderivative, var, pool) {
return String::new();
}
let Ok(derived_diff) = crate::diff::diff(antiderivative, var, pool) else {
return String::new();
};
if derived_diff.value != integrand {
return String::new();
}
let cert = emit_lean_expr_wrt(&derived_diff, pool, Some(var));
if cert.is_empty() {
return String::new();
}
let f = expr_to_lean(integrand, pool);
let big_f = expr_to_lean(antiderivative, pool);
let var_name = pool.with(var, |d| match d {
ExprData::Symbol { name, .. } => name.clone(),
_ => "x".to_string(),
});
let note = format!(
"-- ∫ {f} d{var_name} = {big_f}\n\
-- certified via the FTC derivative relation: deriv (fun {var_name} => {big_f}) {var_name} = {f}\n"
);
match cert.find("-- Step 1") {
Some(idx) => format!("{}{note}{}", &cert[..idx], &cert[idx..]),
None => format!("{cert}{note}"),
}
}
fn emit_definite_integral_header() -> String {
"import Mathlib.Tactic\n\
import Mathlib.Analysis.SpecialFunctions.Trigonometric.Deriv\n\
import Mathlib.Analysis.SpecialFunctions.ExpDeriv\n\
import Mathlib.Analysis.Calculus.Deriv.Pow\n\
import Mathlib.MeasureTheory.Integral.IntervalIntegral\n\
import Mathlib.MeasureTheory.Integral.FundThmCalculus\n\
\n\
open Real\n\n"
.to_string()
}
enum DefiniteIntegrandClass {
Cos,
Sin,
Exp,
Pow(i64),
}
fn classify_definite_integrand(
integrand: ExprId,
var: ExprId,
pool: &ExprPool,
) -> Option<DefiniteIntegrandClass> {
pool.with(integrand, |d| match d {
ExprData::Symbol { .. } if integrand == var => Some(DefiniteIntegrandClass::Pow(1)),
ExprData::Func { name, args } if args.len() == 1 && args[0] == var => match name.as_str() {
"sin" => Some(DefiniteIntegrandClass::Sin),
"cos" => Some(DefiniteIntegrandClass::Cos),
"exp" => Some(DefiniteIntegrandClass::Exp),
_ => None,
},
ExprData::Pow { base, exp } if *base == var => pool.with(*exp, |e| match e {
ExprData::Integer(n) => {
n.0.to_i64()
.filter(|&k| k >= 1)
.map(DefiniteIntegrandClass::Pow)
}
_ => None,
}),
_ => None,
})
}
fn bound_is_infinite(bound: ExprId, pool: &ExprPool) -> bool {
if bound == pool.pos_infinity() {
return true;
}
pool.with(bound, |d| match d {
ExprData::Add(xs) | ExprData::Mul(xs) => xs.iter().any(|&c| bound_is_infinite(c, pool)),
ExprData::Pow { base, exp } => {
bound_is_infinite(*base, pool) || bound_is_infinite(*exp, pool)
}
ExprData::Func { args, .. } => args.iter().any(|&a| bound_is_infinite(a, pool)),
_ => false,
})
}
pub fn emit_definite_integration_cert(
integrand: ExprId,
var: ExprId,
lower: ExprId,
upper: ExprId,
pool: &ExprPool,
) -> String {
if bound_is_infinite(lower, pool) || bound_is_infinite(upper, pool) {
return String::new();
}
let Some(class) = classify_definite_integrand(integrand, var, pool) else {
return String::new();
};
let var_name = pool.with(var, |d| match d {
ExprData::Symbol { name, .. } => name.clone(),
_ => "x".to_string(),
});
let a_lean = expr_to_lean(lower, pool);
let b_lean = expr_to_lean(upper, pool);
let f_lean = expr_to_lean(integrand, pool);
#[allow(clippy::type_complexity)]
let (antideriv_body, hderiv_close, hint_expr): (
Box<dyn Fn(&str) -> String>,
String,
String,
) = match class {
DefiniteIntegrandClass::Cos => (
Box::new(|t: &str| format!("Real.sin ({t})")),
format!("exact Real.hasDerivAt_sin {var_name}"),
"Real.continuous_cos.intervalIntegrable _ _".to_string(),
),
DefiniteIntegrandClass::Sin => (
Box::new(|t: &str| format!("-Real.cos ({t})")),
format!("simpa using (Real.hasDerivAt_cos {var_name}).neg"),
"Real.continuous_sin.intervalIntegrable _ _".to_string(),
),
DefiniteIntegrandClass::Exp => (
Box::new(|t: &str| format!("Real.exp ({t})")),
format!("exact Real.hasDerivAt_exp {var_name}"),
"Real.continuous_exp.intervalIntegrable _ _".to_string(),
),
DefiniteIntegrandClass::Pow(n) => {
let m = n + 1;
(
Box::new(move |t: &str| format!("({t}) ^ ({m} : ℕ) / ({m})")),
format!("simpa using (hasDerivAt_pow {m} {var_name}).div_const {m}"),
if n == 1 {
"continuous_id.intervalIntegrable _ _".to_string()
} else {
format!("(continuous_pow {n}).intervalIntegrable _ _")
},
)
}
};
let f_body = antideriv_body(&var_name);
let rhs = format!(
"({}) - ({})",
antideriv_body(&b_lean),
antideriv_body(&a_lean)
);
let mut out = emit_definite_integral_header();
out.push_str(&format!(
"-- ∫ {var_name} in {a_lean}..{b_lean}, {f_lean} = F {b_lean} - F {a_lean} (F = fun {var_name} => {f_body})\n\
-- certified via the second FTC for interval integrals:\n\
-- intervalIntegral.integral_eq_sub_of_hasDerivAt (deriv F = f on uIcc) (IntervalIntegrable f)\n"
));
out.push_str(&format!(
"example : ∫ {var_name} in ({a_lean})..({b_lean}), {f_lean} = {rhs} := by\n\
\x20 have hderiv : ∀ {var_name} ∈ Set.uIcc ({a_lean}) ({b_lean}),\n\
\x20 HasDerivAt (fun ({var_name} : ℝ) => {f_body}) ({f_lean}) {var_name} := by\n\
\x20 intro {var_name} _\n\
\x20 {hderiv_close}\n\
\x20 have hint : IntervalIntegrable (fun ({var_name} : ℝ) => {f_lean}) MeasureTheory.volume ({a_lean}) ({b_lean}) :=\n\
\x20 {hint_expr}\n\
\x20 exact intervalIntegral.integral_eq_sub_of_hasDerivAt hderiv hint\n"
));
if out.contains("sorry") || out.contains("admit") {
return String::new();
}
out
}
fn expr_to_lean(expr: ExprId, pool: &ExprPool) -> String {
pool.with(expr, |data| match data {
ExprData::Integer(n) => {
let v = n.0.to_i64().unwrap_or(0);
format!("({v} : ℝ)")
}
ExprData::Rational(r) => {
let n = r.0.numer().to_i64().unwrap_or(0);
let d = r.0.denom().to_i64().unwrap_or(1);
format!("({n} / {d} : ℝ)")
}
ExprData::Float(f) => format!("({} : ℝ)", f.inner),
ExprData::Symbol { name, .. } => format!("({name} : ℝ)"),
ExprData::Add(args) => {
let parts: Vec<String> = args.iter().map(|&a| expr_to_lean(a, pool)).collect();
format!("({})", parts.join(" + "))
}
ExprData::Mul(args) => {
let parts: Vec<String> = args.iter().map(|&a| expr_to_lean(a, pool)).collect();
format!("({})", parts.join(" * "))
}
ExprData::Pow { base, exp } => {
let b = expr_to_lean(*base, pool);
let neg_int = pool.with(*exp, |d| match d {
ExprData::Integer(n) if n.0 < 0 => n.0.to_i64(),
_ => None,
});
if let Some(n) = neg_int {
let abs_n = n.unsigned_abs();
if abs_n == 1 {
format!("({b})⁻¹")
} else {
format!("({b})⁻¹ ^ ({abs_n} : ℕ)")
}
} else {
let e = pool.with(*exp, |d| match d {
ExprData::Integer(n) if n.0 >= 0 => format!("({} : ℕ)", n.0),
_ => expr_to_lean(*exp, pool),
});
format!("({b}) ^ {e}")
}
}
ExprData::Func { name, args } => {
let arg_strs: Vec<String> = args.iter().map(|&a| expr_to_lean(a, pool)).collect();
match name.as_str() {
"sin" => format!("Real.sin ({})", arg_strs[0]),
"cos" => format!("Real.cos ({})", arg_strs[0]),
"tan" => format!("Real.tan ({})", arg_strs[0]),
"exp" => format!("Real.exp ({})", arg_strs[0]),
"log" => format!("Real.log ({})", arg_strs[0]),
"sqrt" => format!("Real.sqrt ({})", arg_strs[0]),
"gamma" => format!("Real.Gamma ({})", arg_strs[0]),
other => format!("{other} ({})", arg_strs.join(", ")),
}
}
_ => "sorry".to_string(),
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::kernel::{Domain, ExprPool};
use crate::simplify::simplify;
fn p() -> ExprPool {
ExprPool::new()
}
#[test]
fn emit_lean_const_fold() {
let pool = p();
let two = pool.integer(2_i32);
let three = pool.integer(3_i32);
let expr = pool.add(vec![two, three]);
let derived = simplify(expr, &pool);
let lean = emit_lean_expr(&derived, &pool);
assert!(
lean.contains("import Mathlib.Tactic"),
"missing import: {lean}"
);
assert!(
lean.contains("ring"),
"ConstFold should produce a ring proof: {lean}"
);
}
#[test]
fn emit_lean_add_zero() {
let pool = p();
let x = pool.symbol("x", Domain::Real);
let zero = pool.integer(0_i32);
let expr = pool.add(vec![x, zero]);
let derived = simplify(expr, &pool);
let lean = emit_lean_expr(&derived, &pool);
assert!(
lean.contains("add_zero") || lean.contains("simp"),
"missing add_zero tactic: {lean}"
);
assert!(
!lean.contains("simp_all [*]"),
"Lean 4 does not parse `simp_all [*]`; emit only per-step examples ({lean})"
);
}
#[test]
fn emit_header_has_imports() {
let h = emit_header();
assert!(h.contains("import Mathlib.Tactic"));
assert!(h.contains("open Real"));
}
#[test]
fn emit_step_fires() {
let pool = p();
let x = pool.symbol("x", Domain::Real);
let zero = pool.integer(0_i32);
let before = pool.add(vec![x, zero]);
let step = crate::deriv::log::RewriteStep::simple("add_zero", before, x);
let s = emit_step(&step, &pool);
assert!(s.contains("add_zero"));
assert!(s.contains("simp"));
}
#[test]
fn emit_lean_diff_univariate_poly() {
use crate::diff::diff;
let pool = p();
let x = pool.symbol("x", Domain::Real);
let three = pool.integer(3_i32);
let expr = pool.pow(x, three);
let derived = diff(expr, x, &pool).expect("diff");
let lean = emit_lean_expr_wrt(&derived, &pool, Some(x));
assert!(
lean.contains("deriv (fun (x : ℝ)"),
"expected deriv goal, got: {lean}"
);
assert!(
lean.contains("deriv_pow"),
"expected deriv_pow tactic, got: {lean}"
);
assert!(
!lean.contains("sorry"),
"polynomial derivative certificate must not use an admission: {lean}"
);
assert!(
!lean.contains("= (((x : ℝ)) ^ (2 : ℕ) * (3 : ℝ)) :=") || lean.contains("deriv"),
"must not claim x^3 = 3*x^2 without deriv: {lean}"
);
}
#[test]
fn withhold_false_integrate_sin_certificate() {
use crate::integrate::integrate;
let pool = p();
let x = pool.symbol("x", Domain::Real);
let sin_x = pool.func("sin", vec![x]);
let derived = integrate(sin_x, x, &pool).expect("integrate");
let lean = emit_lean_expr(&derived, &pool);
assert!(
lean.is_empty(),
"∫ sin must not emit false `sin = -cos` Lean equality, got: {lean}"
);
}
#[test]
fn integration_cert_cos_via_ftc_derivative() {
use crate::integrate::integrate;
let pool = p();
let x = pool.symbol("x", Domain::Real);
let cos_x = pool.func("cos", vec![x]);
let derived = integrate(cos_x, x, &pool).expect("integrate");
let lean = emit_integration_cert(derived.value, cos_x, x, &pool);
assert!(
!lean.is_empty(),
"∫ cos x should certify via the FTC relation"
);
assert!(
lean.contains("deriv (fun (x : ℝ)"),
"must state the derivative relation, got: {lean}"
);
assert!(
lean.contains("Real.deriv_sin"),
"antiderivative sin is discharged by Real.deriv_sin: {lean}"
);
assert!(!lean.contains("sorry") && !lean.contains("admit"));
}
#[test]
fn integration_cert_sin_via_ftc_derivative() {
use crate::integrate::integrate;
let pool = p();
let x = pool.symbol("x", Domain::Real);
let sin_x = pool.func("sin", vec![x]);
let derived = integrate(sin_x, x, &pool).expect("integrate");
let lean = emit_integration_cert(derived.value, sin_x, x, &pool);
assert!(
!lean.is_empty(),
"∫ sin x should certify via the FTC relation, got empty"
);
assert!(
lean.contains("deriv (fun (x : ℝ)"),
"must state the derivative relation: {lean}"
);
assert!(!lean.contains("sorry") && !lean.contains("admit"));
}
#[test]
fn integration_cert_exp_via_ftc_derivative() {
use crate::integrate::integrate;
let pool = p();
let x = pool.symbol("x", Domain::Real);
let exp_x = pool.func("exp", vec![x]);
let derived = integrate(exp_x, x, &pool).expect("integrate");
let lean = emit_integration_cert(derived.value, exp_x, x, &pool);
assert!(
!lean.is_empty(),
"∫ exp x should certify via the FTC relation"
);
assert!(
lean.contains("Real.deriv_exp"),
"expected deriv_exp: {lean}"
);
assert!(!lean.contains("sorry") && !lean.contains("admit"));
}
#[test]
fn integration_cert_power_via_ftc_derivative() {
use crate::integrate::integrate;
let pool = p();
let x = pool.symbol("x", Domain::Real);
let x2 = pool.pow(x, pool.integer(2_i32));
let derived = integrate(x2, x, &pool).expect("integrate");
let lean = emit_integration_cert(derived.value, x2, x, &pool);
assert!(!lean.is_empty(), "∫ x² should certify via the FTC relation");
assert!(
lean.contains("deriv (fun (x : ℝ)"),
"must state the derivative relation: {lean}"
);
assert!(!lean.contains("sorry") && !lean.contains("admit"));
}
#[test]
fn definite_integration_cert_cos() {
let pool = p();
let x = pool.symbol("x", Domain::Real);
let cos_x = pool.func("cos", vec![x]);
let lean = emit_definite_integration_cert(
cos_x,
x,
pool.integer(0_i32),
pool.integer(1_i32),
&pool,
);
assert!(
!lean.is_empty(),
"∫₀¹ cos must certify via the interval FTC"
);
assert!(
lean.contains("intervalIntegral.integral_eq_sub_of_hasDerivAt"),
"must invoke the interval FTC lemma: {lean}"
);
assert!(
lean.contains("HasDerivAt (fun (x : ℝ) => Real.sin (x))"),
"antiderivative is sin: {lean}"
);
assert!(
lean.contains("Real.hasDerivAt_sin"),
"cos derivative witness: {lean}"
);
assert!(!lean.contains("sorry") && !lean.contains("admit"));
}
#[test]
fn definite_integration_cert_sin() {
let pool = p();
let x = pool.symbol("x", Domain::Real);
let sin_x = pool.func("sin", vec![x]);
let lean = emit_definite_integration_cert(
sin_x,
x,
pool.integer(0_i32),
pool.integer(1_i32),
&pool,
);
assert!(
!lean.is_empty(),
"∫₀¹ sin must certify via the interval FTC"
);
assert!(
lean.contains("intervalIntegral.integral_eq_sub_of_hasDerivAt"),
"must invoke the interval FTC lemma: {lean}"
);
assert!(
lean.contains("Real.hasDerivAt_cos"),
"sin derivative witness comes from cos: {lean}"
);
assert!(!lean.contains("sorry") && !lean.contains("admit"));
}
#[test]
fn definite_integration_cert_power() {
let pool = p();
let x = pool.symbol("x", Domain::Real);
let x2 = pool.pow(x, pool.integer(2_i32));
let lean =
emit_definite_integration_cert(x2, x, pool.integer(0_i32), pool.integer(1_i32), &pool);
assert!(!lean.is_empty(), "∫₀¹ x² must certify via the interval FTC");
assert!(
lean.contains("hasDerivAt_pow"),
"power derivative witness: {lean}"
);
assert!(
lean.contains("continuous_pow"),
"power integrability via continuous_pow: {lean}"
);
assert!(!lean.contains("sorry") && !lean.contains("admit"));
}
#[test]
fn definite_integration_cert_exp() {
let pool = p();
let x = pool.symbol("x", Domain::Real);
let exp_x = pool.func("exp", vec![x]);
let lean = emit_definite_integration_cert(
exp_x,
x,
pool.integer(0_i32),
pool.integer(1_i32),
&pool,
);
assert!(
!lean.is_empty(),
"∫₀¹ exp must certify via the interval FTC"
);
assert!(
lean.contains("Real.hasDerivAt_exp"),
"exp derivative witness: {lean}"
);
assert!(!lean.contains("sorry") && !lean.contains("admit"));
}
#[test]
fn definite_integration_cert_withheld_for_unsupported_integrand() {
let pool = p();
let x = pool.symbol("x", Domain::Real);
let log_x = pool.func("log", vec![x]);
let lean = emit_definite_integration_cert(
log_x,
x,
pool.integer(1_i32),
pool.integer(2_i32),
&pool,
);
assert!(
lean.is_empty(),
"∫ log x is outside the certifiable fragment; must withhold: {lean}"
);
}
#[test]
fn definite_integration_cert_withheld_for_infinite_bound() {
let pool = p();
let x = pool.symbol("x", Domain::Real);
let exp_neg_x = pool.func("exp", vec![x]);
let lean = emit_definite_integration_cert(
exp_neg_x,
x,
pool.integer(0_i32),
pool.pos_infinity(),
&pool,
);
assert!(
lean.is_empty(),
"∫ with an infinite bound must withhold the interval-FTC cert: {lean}"
);
}
#[test]
fn integration_cert_withheld_for_chain_composite_antiderivative() {
use crate::integrate::integrate;
let pool = p();
let x = pool.symbol("x", Domain::Real);
let x2 = pool.pow(x, pool.integer(2_i32));
let integrand = pool.mul(vec![x, pool.func("exp", vec![x2])]);
let derived = integrate(integrand, x, &pool).expect("integrate");
let lean = emit_integration_cert(derived.value, integrand, x, &pool);
assert!(
lean.is_empty(),
"∫ x·exp(x²) has a composite antiderivative; must withhold: {lean}"
);
}
#[test]
fn integration_cert_withheld_for_non_certifiable_diff() {
use crate::integrate::integrate;
let pool = p();
let x = pool.symbol("x", Domain::Real);
let log_x = pool.func("log", vec![x]);
let derived = integrate(log_x, x, &pool).expect("integrate");
let lean = emit_integration_cert(derived.value, log_x, x, &pool);
assert!(
lean.is_empty(),
"∫ log x's antiderivative differentiates via diff_log; must withhold: {lean}"
);
}
#[test]
fn emit_lean_diff_sin_without_sorry() {
use crate::diff::diff;
let pool = p();
let x = pool.symbol("x", Domain::Real);
let sin_x = pool.func("sin", vec![x]);
let derived = diff(sin_x, x, &pool).expect("diff");
let lean = emit_lean_expr_wrt(&derived, &pool, Some(x));
assert!(!lean.is_empty(), "d/dx sin(x) should be Lean-certifiable");
assert!(
!lean.contains("sorry"),
"d/dx sin(x) certificate must not use sorry: {lean}"
);
assert!(
lean.contains("Real.deriv_sin"),
"expected Real.deriv_sin tactic: {lean}"
);
if let Some(mul_one_block) = lean.split("-- Step").find(|b| b.contains(": mul_one\n")) {
assert!(
!mul_one_block.contains("deriv (fun"),
"mul_one cleanup must be a plain equality, got: {mul_one_block}"
);
}
}
#[test]
fn emit_lean_parens_nested_log_exp() {
use crate::simplify::{rulesets::log_exp_rules, simplify_with, SimplifyConfig};
let pool = p();
let x = pool.symbol("x", Domain::Real);
let expr = pool.func("log", vec![pool.func("exp", vec![x])]);
let derived = simplify_with(expr, &pool, &log_exp_rules(), SimplifyConfig::default());
let lean = emit_lean_expr(&derived, &pool);
assert!(!lean.is_empty(), "log(exp(x)) should be Lean-certifiable");
assert!(
lean.contains("Real.log (Real.exp"),
"nested funcs must be parenthesized, got: {lean}"
);
assert!(
!lean.contains("Real.log Real.exp "),
"unparenthesized application is a type error: {lean}"
);
}
#[test]
fn exp_of_log_certifies_with_positivity_hyp() {
use crate::kernel::expr::PredicateKind;
use crate::simplify::AssumptionContext;
let pool = p();
let x = pool.symbol("x", Domain::Real);
let zero = pool.integer(0_i32);
let mut assumptions = AssumptionContext::new();
assumptions
.refine(pool.predicate(PredicateKind::Gt, vec![x, zero]), &pool)
.unwrap();
let expr = pool.func("exp", vec![pool.func("log", vec![x])]);
let derived = assumptions.simplify(expr, &pool);
let lean = emit_lean_expr(&derived, &pool);
assert!(
!lean.is_empty(),
"exp(log(x)) with a recorded positivity condition should certify"
);
assert!(
!lean.contains("sorry"),
"certificate must not use sorry: {lean}"
);
assert!(
lean.contains("(hx : 0 < x)"),
"expected an explicit positivity binder: {lean}"
);
assert!(
lean.contains("Real.exp_log hx"),
"expected Real.exp_log to consume the hypothesis: {lean}"
);
}
#[test]
fn exp_of_log_withheld_when_positivity_unproven() {
let pool = p();
let x = pool.symbol("x", Domain::Real);
let expr = pool.func("exp", vec![pool.func("log", vec![x])]);
let step = RewriteStep::simple("exp_of_log", expr, x);
let lean = emit_step(&step, &pool);
assert!(
lean.contains("sorry"),
"step without a positivity side condition must fall back to sorry: {lean}"
);
}
#[test]
fn log_of_product_certifies_two_factors_under_positivity() {
use crate::kernel::expr::PredicateKind;
use crate::simplify::AssumptionContext;
let pool = p();
let x = pool.symbol("x", Domain::Real);
let y = pool.symbol("y", Domain::Real);
let zero = pool.integer(0_i32);
let mut assumptions = AssumptionContext::new();
assumptions
.refine(pool.predicate(PredicateKind::Gt, vec![x, zero]), &pool)
.unwrap();
assumptions
.refine(pool.predicate(PredicateKind::Gt, vec![y, zero]), &pool)
.unwrap();
let expr = pool.func("log", vec![pool.mul(vec![x, y])]);
let derived = assumptions.simplify(expr, &pool);
let lean = emit_lean_expr(&derived, &pool);
assert!(!lean.is_empty(), "log(x*y) should certify under x>0, y>0");
assert!(
!lean.contains("sorry"),
"certificate must not use sorry: {lean}"
);
assert!(
lean.contains("(hx : 0 < x)") && lean.contains("(hy : 0 < y)"),
"expected explicit positivity binders: {lean}"
);
assert!(
lean.contains("Real.log_mul (ne_of_gt hx) (ne_of_gt hy)"),
"expected Real.log_mul to consume both hypotheses: {lean}"
);
}
#[test]
fn log_of_product_withheld_for_three_factors() {
let pool = p();
let x = pool.symbol("x", Domain::Real);
let y = pool.symbol("y", Domain::Real);
let z = pool.symbol("z", Domain::Real);
let before = pool.func("log", vec![pool.mul(vec![x, y, z])]);
let after = pool.add(vec![
pool.func("log", vec![x]),
pool.func("log", vec![y]),
pool.func("log", vec![z]),
]);
let step = RewriteStep::with_conditions(
"log_of_product",
before,
after,
vec![
SideCondition::Positive(x),
SideCondition::Positive(y),
SideCondition::Positive(z),
],
);
let lean = emit_step(&step, &pool);
assert!(
lean.contains("sorry"),
"three-factor log_of_product has no known lemma yet; must withhold: {lean}"
);
}
#[test]
fn sum_of_logs_certifies_with_positivity_hyp() {
use crate::kernel::expr::PredicateKind;
use crate::simplify::AssumptionContext;
let pool = p();
let x = pool.symbol("x", Domain::Real);
let y = pool.symbol("y", Domain::Real);
let zero = pool.integer(0_i32);
let mut assumptions = AssumptionContext::new();
assumptions
.refine(pool.predicate(PredicateKind::Gt, vec![x, zero]), &pool)
.unwrap();
assumptions
.refine(pool.predicate(PredicateKind::Gt, vec![y, zero]), &pool)
.unwrap();
let expr = pool.add(vec![pool.func("log", vec![x]), pool.func("log", vec![y])]);
let derived = assumptions.simplify(expr, &pool);
let lean = emit_lean_expr(&derived, &pool);
assert!(!lean.is_empty(), "log x + log y should certify under x,y>0");
assert!(
!lean.contains("sorry"),
"certificate must not use sorry: {lean}"
);
assert!(
lean.contains("(hx : 0 < x)") && lean.contains("(hy : 0 < y)"),
"expected explicit positivity binders: {lean}"
);
assert!(
lean.contains("Real.log_mul (ne_of_gt hx) (ne_of_gt hy)"),
"expected Real.log_mul to consume both hypotheses: {lean}"
);
}
#[test]
fn product_of_exps_certifies_with_exp_add() {
use crate::simplify::{rulesets::log_exp_rules, simplify_with, SimplifyConfig};
let pool = p();
let x = pool.symbol("x", Domain::Real);
let y = pool.symbol("y", Domain::Real);
let expr = pool.mul(vec![pool.func("exp", vec![x]), pool.func("exp", vec![y])]);
let derived = simplify_with(expr, &pool, &log_exp_rules(), SimplifyConfig::default());
let lean = emit_lean_expr(&derived, &pool);
assert!(!lean.is_empty(), "exp x * exp y should certify");
assert!(
!lean.contains("sorry"),
"certificate must not use sorry: {lean}"
);
assert!(
lean.contains("← Real.exp_add"),
"expected exp_add fold: {lean}"
);
}
#[test]
fn inv_cancel_x_squared_times_x_neg_squared_certifies_with_nonzero_hyp() {
use crate::simplify::simplify;
let pool = p();
let x = pool.symbol("x", Domain::Real);
let expr = pool.mul(vec![
pool.pow(x, pool.integer(2_i32)),
pool.pow(x, pool.integer(-2_i32)),
]);
let derived = simplify(expr, &pool);
let lean = emit_lean_expr(&derived, &pool);
assert!(!lean.is_empty(), "x² * x⁻² = 1 should certify under x ≠ 0");
assert!(
!lean.contains("sorry"),
"certificate must not use sorry: {lean}"
);
assert!(
lean.contains("(hne : x ≠ 0)"),
"expected an explicit nonzero-hypothesis binder: {lean}"
);
assert!(
lean.contains("field_simp [hne]"),
"expected field_simp to consume the hypothesis: {lean}"
);
}
#[test]
fn inv_cancel_mixed_sign_exponents_needs_trailing_ring() {
use crate::simplify::simplify;
let pool = p();
let x = pool.symbol("x", Domain::Real);
let expr = pool.mul(vec![
pool.pow(x, pool.integer(-2_i32)),
pool.pow(x, pool.integer(5_i32)),
]);
let derived = simplify(expr, &pool);
let lean = emit_lean_expr(&derived, &pool);
assert!(!lean.is_empty(), "x⁻² * x⁵ = x³ should certify under x ≠ 0");
assert!(
!lean.contains("sorry"),
"certificate must not use sorry: {lean}"
);
assert!(
lean.contains("(hne : x ≠ 0)"),
"expected an explicit nonzero-hypothesis binder: {lean}"
);
assert!(
lean.contains("field_simp [hne]") && lean.contains("ring"),
"expected field_simp followed by a closing ring: {lean}"
);
}
#[test]
fn double_inverse_certifies_unconditionally() {
use crate::simplify::simplify;
let pool = p();
let x = pool.symbol("x", Domain::Real);
let expr = pool.pow(pool.pow(x, pool.integer(-1_i32)), pool.integer(-1_i32));
let derived = simplify(expr, &pool);
let lean = emit_lean_expr(&derived, &pool);
assert!(
!lean.is_empty(),
"(x⁻¹)⁻¹ = x should certify unconditionally"
);
assert!(
!lean.contains("sorry"),
"certificate must not use sorry: {lean}"
);
assert!(
!lean.contains("(hne"),
"double inverse needs no hypothesis binder: {lean}"
);
assert!(
lean.contains("simp [inv_inv]"),
"expected the inv_inv simp lemma: {lean}"
);
}
#[test]
fn gamma_maps_to_real_gamma_and_imports() {
use crate::deriv::log::DerivedExpr;
let pool = p();
let k = pool.symbol("k", Domain::Real);
let one = pool.integer(1_i32);
let expr = pool.mul(vec![k, pool.func("gamma", vec![pool.add(vec![k, one])])]);
assert!(
expr_to_lean(expr, &pool).contains("Real.Gamma"),
"gamma must map to Real.Gamma"
);
let derived = DerivedExpr::new(expr);
let lean = emit_lean_expr(&derived, &pool);
assert!(
lean.contains("import Mathlib.Analysis.SpecialFunctions.Gamma.Basic"),
"header must import Gamma: {lean}"
);
assert!(
lean.contains("Real.Gamma") && !lean.contains("sorry"),
"gamma reflexivity cert must reference Real.Gamma without sorry: {lean}"
);
}
#[test]
fn diff_goal_names_unused_binder_underscore() {
let pool = p();
let x = pool.symbol("x", Domain::Real);
let c = pool.symbol("C1", Domain::Real);
let zero = pool.integer(0_i32);
let goal = emit_diff_goal(c, zero, x, &pool);
assert!(
goal.contains("fun (_x : ℝ)"),
"unused binder must be underscore-prefixed: {goal}"
);
assert!(
goal.contains(") x = "),
"eval point must remain the bare variable: {goal}"
);
let sin_x = pool.func("sin", vec![x]);
let one = pool.integer(1_i32);
let used = emit_diff_goal(sin_x, one, x, &pool);
assert!(
used.contains("fun (x : ℝ)"),
"a used binder must not be renamed: {used}"
);
}
#[test]
fn emit_lean_diff_x_squared_closes_with_ring() {
use crate::diff::diff;
let pool = p();
let x = pool.symbol("x", Domain::Real);
let expr = pool.pow(x, pool.integer(2_i32));
let derived = diff(expr, x, &pool).expect("diff");
let lean = emit_lean_expr_wrt(&derived, &pool, Some(x));
assert!(!lean.is_empty(), "d/dx x² should be Lean-certifiable");
assert!(
lean.contains("try ring") || lean.contains("; ring"),
"x² coeff order needs ring: {lean}"
);
}
#[test]
fn emit_lean_sum_rule_sin_cos() {
use crate::diff::diff;
let pool = p();
let x = pool.symbol("x", Domain::Real);
let expr = pool.add(vec![pool.func("sin", vec![x]), pool.func("cos", vec![x])]);
let derived = diff(expr, x, &pool).expect("diff");
let lean = emit_lean_expr_wrt(&derived, &pool, Some(x));
assert!(
!lean.is_empty(),
"d/dx (sin+cos) should be Lean-certifiable"
);
assert!(
lean.contains("differentiableAt_sin") || lean.contains("deriv_add"),
"sum_rule needs DifferentiableAt lemmas: {lean}"
);
assert!(
!lean.contains("sorry"),
"sum certificate must not use sorry: {lean}"
);
}
#[test]
fn emit_lean_product_rule_sin_exp() {
use crate::diff::diff;
let pool = p();
let x = pool.symbol("x", Domain::Real);
let expr = pool.mul(vec![pool.func("sin", vec![x]), pool.func("exp", vec![x])]);
let derived = diff(expr, x, &pool).expect("diff");
let lean = emit_lean_expr_wrt(&derived, &pool, Some(x));
assert!(
!lean.is_empty(),
"d/dx (sin·exp) should be Lean-certifiable after product_rule fix"
);
assert!(
lean.contains("deriv_mul"),
"expected product_rule deriv_mul tactic: {lean}"
);
assert!(
!lean.contains("sorry"),
"product certificate must not use sorry: {lean}"
);
}
#[test]
fn multi_term_poly_combine_certifies_via_discharge_depth() {
use crate::diff::diff;
let pool = p();
let x = pool.symbol("x", Domain::Real);
let expr = pool.add(vec![
pool.mul(vec![pool.integer(3_i32), pool.pow(x, pool.integer(3_i32))]),
pool.mul(vec![pool.integer(2_i32), pool.pow(x, pool.integer(2_i32))]),
pool.mul(vec![x, pool.integer(5_i32)]),
pool.integer(7_i32),
]);
let derived = diff(expr, x, &pool).expect("diff");
let lean = emit_lean_expr_wrt(&derived, &pool, Some(x));
assert!(
!lean.is_empty(),
"multi-term polynomial derivative must be certifiable: {lean}"
);
assert!(
lean.contains("maxDischargeDepth"),
"combine steps must use the raised discharge-depth tactic: {lean}"
);
assert!(!lean.contains("sorry"), "must not admit: {lean}");
}
#[test]
fn log_in_product_combine_is_withheld() {
use crate::diff::diff;
let pool = p();
let x = pool.symbol("x", Domain::Real);
let expr = pool.mul(vec![x, pool.func("log", vec![x])]);
let derived = diff(expr, x, &pool).expect("diff");
let lean = emit_lean_expr_wrt(&derived, &pool, Some(x));
assert!(
lean.is_empty(),
"d/dx (x·log x) must be withheld (needs x ≠ 0), got: {lean}"
);
}
#[test]
fn negative_power_of_var_diff_is_withheld() {
use crate::diff::diff;
let pool = p();
let x = pool.symbol("x", Domain::Real);
let expr = pool.pow(x, pool.integer(-2_i32));
let derived = diff(expr, x, &pool).expect("diff");
let lean = emit_lean_expr_wrt(&derived, &pool, Some(x));
assert!(
lean.is_empty(),
"d/dx (x⁻²) must be withheld (needs x ≠ 0), got: {lean}"
);
}
#[test]
fn emit_lean_tan_expand_uses_div_eq_mul_inv() {
use crate::simplify::{rulesets::trig_rules, simplify_with, SimplifyConfig};
let pool = p();
let x = pool.symbol("x", Domain::Real);
let expr = pool.func("tan", vec![x]);
let derived = simplify_with(expr, &pool, &trig_rules(), SimplifyConfig::default());
let lean = emit_lean_expr(&derived, &pool);
assert!(!lean.is_empty(), "tan(x) expand should be Lean-certifiable");
assert!(
lean.contains("div_eq_mul_inv"),
"tan→sin/cos needs div_eq_mul_inv for reciprocal form: {lean}"
);
assert!(
lean.contains("Real.tan"),
"tan must emit Real.tan, got: {lean}"
);
}
#[test]
fn emit_lean_log_pow_parenthesized() {
use crate::simplify::{rulesets::log_exp_rules, simplify_with, SimplifyConfig};
let pool = p();
let x = pool.symbol("x", Domain::Real);
let expr = pool.func("log", vec![pool.pow(x, pool.integer(3_i32))]);
let derived = simplify_with(expr, &pool, &log_exp_rules(), SimplifyConfig::default());
let lean = emit_lean_expr(&derived, &pool);
assert!(!lean.is_empty(), "log(x^3) should be Lean-certifiable");
assert!(
lean.contains("Real.log (") && lean.contains("^"),
"log of a power must keep the power inside the log arg: {lean}"
);
assert!(
!lean.contains("Real.log (x : ℝ)) ^") && !lean.contains("Real.log x ^"),
"power must not bind tighter than log: {lean}"
);
}
#[test]
fn generalized_power_rule_on_sin_squared_certifies() {
use crate::diff::diff;
let pool = p();
let x = pool.symbol("x", Domain::Real);
let sin_x = pool.func("sin", vec![x]);
let expr = pool.pow(sin_x, pool.integer(2_i32));
let derived = diff(expr, x, &pool).expect("diff");
let lean = emit_lean_expr_wrt(&derived, &pool, Some(x));
assert!(
!lean.is_empty(),
"d/dx sin(x)² should now be Lean-certifiable via HasDerivAt.pow"
);
assert!(
!lean.contains("sorry"),
"sin(x)² certificate must not use sorry: {lean}"
);
assert!(
lean.contains("hf.pow 2"),
"expected HasDerivAt.pow composition: {lean}"
);
}
#[test]
fn inv_of_primitive_on_one_over_sin_certifies_with_nonzero_hyp() {
use crate::diff::diff;
let pool = p();
let x = pool.symbol("x", Domain::Real);
let sin_x = pool.func("sin", vec![x]);
let expr = pool.pow(sin_x, pool.integer(-1_i32));
let derived = diff(expr, x, &pool).expect("diff");
let lean = emit_lean_expr_wrt(&derived, &pool, Some(x));
assert!(
!lean.is_empty(),
"d/dx (1/sin x) should be Lean-certifiable via HasDerivAt.inv"
);
assert!(
!lean.contains("sorry"),
"1/sin(x) certificate must not use sorry: {lean}"
);
assert!(
lean.contains("(hne : Real.sin x ≠ 0)"),
"expected an explicit nonzero-hypothesis binder: {lean}"
);
assert!(
lean.contains("hf.inv hne"),
"expected HasDerivAt.inv composition: {lean}"
);
}
#[test]
fn quotient_of_primitives_sin_over_cos_certifies() {
use crate::diff::diff;
let pool = p();
let x = pool.symbol("x", Domain::Real);
let sin_x = pool.func("sin", vec![x]);
let cos_x = pool.func("cos", vec![x]);
let expr = pool.mul(vec![sin_x, pool.pow(cos_x, pool.integer(-1_i32))]);
let derived = diff(expr, x, &pool).expect("diff");
let lean = emit_lean_expr_wrt(&derived, &pool, Some(x));
assert!(
!lean.is_empty(),
"d/dx (sin x / cos x) should be Lean-certifiable via HasDerivAt.mul/.inv"
);
assert!(
!lean.contains("sorry"),
"sin/cos certificate must not use sorry: {lean}"
);
assert!(
lean.contains("hf.mul hg"),
"expected the quotient-chain HasDerivAt composition: {lean}"
);
assert!(
lean.contains("field_simp [hne]"),
"expected the cos x * (cos x)⁻¹ cleanup to use field_simp, not bare ring: {lean}"
);
}
#[test]
fn withhold_power_of_primitive_with_unsupported_exponent() {
use crate::diff::diff;
let pool = p();
let x = pool.symbol("x", Domain::Real);
let sin_x = pool.func("sin", vec![x]);
let expr = pool.pow(sin_x, pool.integer(-2_i32));
let derived = diff(expr, x, &pool).expect("diff");
let lean = emit_lean_expr_wrt(&derived, &pool, Some(x));
assert!(
lean.is_empty(),
"d/dx sin(x)^-2 is not encoded; must withhold: {lean}"
);
}
#[test]
fn emit_lean_chain_rule_diff_sin_x_squared() {
use crate::diff::diff;
let pool = p();
let x = pool.symbol("x", Domain::Real);
let x2 = pool.pow(x, pool.integer(2_i32));
let sin_x2 = pool.func("sin", vec![x2]);
let derived = diff(sin_x2, x, &pool).expect("diff");
let lean = emit_lean_expr_wrt(&derived, &pool, Some(x));
assert!(
!lean.is_empty(),
"chain-rule d/dx sin(x²) should now be Lean-certifiable"
);
assert!(
lean.contains("hasDerivAt_pow") && lean.contains("(hg.sin).deriv"),
"expected chain-rule composition tactic: {lean}"
);
assert!(
!lean.contains("sorry"),
"chain-rule certificate must not use sorry: {lean}"
);
}
#[test]
fn emit_lean_chain_rule_diff_exp_x_squared() {
use crate::diff::diff;
let pool = p();
let x = pool.symbol("x", Domain::Real);
let x2 = pool.pow(x, pool.integer(2_i32));
let exp_x2 = pool.func("exp", vec![x2]);
let derived = diff(exp_x2, x, &pool).expect("diff");
let lean = emit_lean_expr_wrt(&derived, &pool, Some(x));
assert!(
!lean.is_empty(),
"chain-rule d/dx exp(x²) should now be Lean-certifiable"
);
assert!(
lean.contains("hasDerivAt_pow") && lean.contains("(hg.exp).deriv"),
"expected exp chain-rule composition tactic: {lean}"
);
assert!(
!lean.contains("sorry"),
"chain-rule certificate must not use sorry: {lean}"
);
}
#[test]
fn withhold_chain_rule_diff_log_composite() {
use crate::diff::diff;
let pool = p();
let x = pool.symbol("x", Domain::Real);
let x2 = pool.pow(x, pool.integer(2_i32));
let log_x2 = pool.func("log", vec![x2]);
let derived = diff(log_x2, x, &pool).expect("diff");
let lean = emit_lean_expr_wrt(&derived, &pool, Some(x));
assert!(
lean.is_empty(),
"chain-rule d/dx log(x²) is not encoded; must withhold: {lean}"
);
}
#[test]
fn diff_log_certifies_unconditionally() {
use crate::diff::diff;
let pool = p();
let x = pool.symbol("x", Domain::Real);
let log_x = pool.func("log", vec![x]);
let derived = diff(log_x, x, &pool).expect("diff");
let lean = emit_lean_expr_wrt(&derived, &pool, Some(x));
assert!(!lean.is_empty(), "d/dx log(x) should be Lean-certifiable");
assert!(
!lean.contains("sorry"),
"log certificate must not use sorry: {lean}"
);
assert!(
lean.contains("Real.deriv_log"),
"expected Real.deriv_log tactic: {lean}"
);
assert!(
lean.contains("example : deriv (fun (x : ℝ) => Real.log"),
"diff_log needs no explicit hypothesis binder: {lean}"
);
}
#[test]
fn diff_sqrt_certifies_with_positivity_hyp() {
use crate::diff::diff;
let pool = p();
let x = pool.symbol("x", Domain::Real);
let sqrt_x = pool.func("sqrt", vec![x]);
let derived = diff(sqrt_x, x, &pool).expect("diff");
let lean = emit_lean_expr_wrt(&derived, &pool, Some(x));
assert!(!lean.is_empty(), "d/dx sqrt(x) should be Lean-certifiable");
assert!(
!lean.contains("sorry"),
"sqrt certificate must not use sorry: {lean}"
);
assert!(
lean.contains("(hx : 0 < x)"),
"expected an explicit positivity binder: {lean}"
);
assert!(
lean.contains("Real.hasDerivAt_sqrt hx.ne'"),
"expected Real.hasDerivAt_sqrt to consume the hypothesis: {lean}"
);
}
#[test]
fn withhold_chain_rule_diff_sqrt_composite() {
use crate::diff::diff;
let pool = p();
let x = pool.symbol("x", Domain::Real);
let x2 = pool.pow(x, pool.integer(2_i32));
let sqrt_x2 = pool.func("sqrt", vec![x2]);
let derived = diff(sqrt_x2, x, &pool).expect("diff");
let lean = emit_lean_expr_wrt(&derived, &pool, Some(x));
assert!(
lean.is_empty(),
"chain-rule d/dx sqrt(x²) is not encoded; must withhold: {lean}"
);
}
#[test]
fn diff_tan_certifies_via_primitive_registry() {
use crate::diff::diff;
let pool = p();
let x = pool.symbol("x", Domain::Real);
let tan_x = pool.func("tan", vec![x]);
let derived = diff(tan_x, x, &pool).expect("diff");
let lean = emit_lean_expr_wrt(&derived, &pool, Some(x));
assert!(!lean.is_empty(), "d/dx tan(x) should be Lean-certifiable");
assert!(
!lean.contains("sorry"),
"tan certificate must not use sorry: {lean}"
);
assert!(
lean.contains("(hne : Real.cos x ≠ 0)"),
"expected an explicit nonzero-hypothesis binder: {lean}"
);
assert!(
lean.contains("Real.hasDerivAt_tan hne") && lean.contains("Real.inv_one_add_tan_sq"),
"expected the tan deriv + Pythagorean-identity reconciliation: {lean}"
);
}
#[test]
fn withhold_chain_rule_diff_tan_composite() {
use crate::diff::diff;
let pool = p();
let x = pool.symbol("x", Domain::Real);
let x2 = pool.pow(x, pool.integer(2_i32));
let tan_x2 = pool.func("tan", vec![x2]);
let derived = diff(tan_x2, x, &pool).expect("diff");
let lean = emit_lean_expr_wrt(&derived, &pool, Some(x));
assert!(
lean.is_empty(),
"chain-rule d/dx tan(x²) is not encoded; must withhold: {lean}"
);
}
#[test]
fn expr_to_lean_integer() {
let pool = p();
let three = pool.integer(3_i32);
let s = expr_to_lean(three, &pool);
assert!(s.contains("3"));
}
#[test]
fn expr_to_lean_sin() {
let pool = p();
let x = pool.symbol("x", Domain::Real);
let sin_x = pool.func("sin", vec![x]);
let s = expr_to_lean(sin_x, &pool);
assert!(s.contains("Real.sin"));
}
#[test]
fn expr_to_lean_pow_natural_exp_is_nat() {
let pool = p();
let x = pool.symbol("x", Domain::Real);
let one = pool.integer(1_i32);
let pow_x_1 = pool.pow(x, one);
let s = expr_to_lean(pow_x_1, &pool);
assert!(
s.contains(": ℕ"),
"expected Nat exponent for HPow ℝ ℕ ℝ, got: {s}"
);
assert!(
s.contains("(x : ℝ)"),
"base must be typed as ℝ so HPow resolves: {s}"
);
assert!(
!s.contains("(1 : ℝ)"),
"Real exponent triggers rpow metavariable issues: {s}"
);
}
#[test]
fn emit_tendsto_exp_neg_x() {
let pool = p();
let x = pool.symbol("x", Domain::Real);
let neg_x = pool.mul(vec![pool.integer(-1_i32), x]);
let expr = pool.func("exp", vec![neg_x]);
let zero = pool.integer(0_i32);
let lean = emit_tendsto_cert(expr, x, zero, &pool);
assert!(
lean.contains("Filter.Tendsto"),
"missing Filter.Tendsto: {lean}"
);
assert!(
lean.contains("tendsto_exp_neg_atTop_nhds_zero"),
"expected known tactic: {lean}"
);
}
#[test]
fn emit_tendsto_exp_x_to_inf() {
let pool = p();
let x = pool.symbol("x", Domain::Real);
let expr = pool.func("exp", vec![x]);
let inf = pool.symbol("∞", Domain::Real);
let lean = emit_tendsto_cert(expr, x, inf, &pool);
assert!(
lean.contains("tendsto_exp_atTop"),
"expected tendsto_exp_atTop: {lean}"
);
}
#[test]
fn emit_tendsto_unrecognized_pattern_withheld() {
let pool = p();
let x = pool.symbol("x", Domain::Real);
let expr = pool.func("sin", vec![x]);
let zero = pool.integer(0_i32);
let lean = emit_tendsto_cert(expr, x, zero, &pool);
assert!(
lean.is_empty(),
"unrecognized patterns must not emit sorry certificates: got {lean}"
);
}
#[test]
fn emit_tendsto_recognized_pattern_yields_cert() {
let pool = p();
let x = pool.symbol("x", Domain::Real);
let neg_x = pool.mul(vec![pool.integer(-1_i32), x]);
let expr = pool.func("exp", vec![neg_x]);
let zero = pool.integer(0_i32);
let lean = emit_tendsto_cert(expr, x, zero, &pool);
assert!(
!lean.is_empty(),
"recognized tendsto patterns must yield a certificate"
);
assert!(
!lean.contains("sorry"),
"recognized pattern certificate must not use sorry: {lean}"
);
}
#[test]
fn emit_tendsto_header_has_filter_imports() {
let h = emit_limit_header();
assert!(h.contains("import Mathlib.Tactic"));
assert!(h.contains("Filter"));
}
}