use thiserror::Error;
#[derive(Debug, Error)]
pub enum ProofError {
#[error("Equation is unprovable: {reason}")]
Unprovable { reason: String },
#[error("Z3 solver timeout after {timeout_ms}ms")]
Timeout { timeout_ms: u64 },
#[error("Z3 internal error: {message}")]
Z3Error { message: String },
#[error("Invalid equation encoding: {message}")]
EncodingError { message: String },
}
#[derive(Debug, Clone)]
pub struct ProofResult {
pub proven: bool,
pub proof_time_us: u64,
pub explanation: String,
pub theorem: String,
}
impl ProofResult {
#[must_use]
pub fn proven(theorem: &str, explanation: &str, proof_time_us: u64) -> Self {
Self {
proven: true,
proof_time_us,
explanation: explanation.to_string(),
theorem: theorem.to_string(),
}
}
}
pub trait Z3Provable {
fn proof_description(&self) -> &'static str;
fn prove_equation(&self) -> Result<ProofResult, ProofError>;
fn theorems(&self) -> Vec<&'static str> {
vec![self.proof_description()]
}
}
#[cfg(feature = "z3-proofs")]
pub mod z3_impl {
use super::{ProofError, ProofResult};
use std::time::Instant;
use z3::ast::Ast;
pub fn prove_two_opt_improvement() -> Result<ProofResult, ProofError> {
let start = Instant::now();
let cfg = z3::Config::new();
let ctx = z3::Context::new(&cfg);
let solver = z3::Solver::new(&ctx);
let d_i_i1 = z3::ast::Real::new_const(&ctx, "d_i_i1"); let d_j_j1 = z3::ast::Real::new_const(&ctx, "d_j_j1"); let d_i_j = z3::ast::Real::new_const(&ctx, "d_i_j"); let d_i1_j1 = z3::ast::Real::new_const(&ctx, "d_i1_j1");
let zero = z3::ast::Real::from_real(&ctx, 0, 1);
solver.assert(&d_i_i1.ge(&zero));
solver.assert(&d_j_j1.ge(&zero));
solver.assert(&d_i_j.ge(&zero));
solver.assert(&d_i1_j1.ge(&zero));
let old_edges = z3::ast::Real::add(&ctx, &[&d_i_i1, &d_j_j1]);
let new_edges = z3::ast::Real::add(&ctx, &[&d_i_j, &d_i1_j1]);
let delta = z3::ast::Real::sub(&ctx, &[&old_edges, &new_edges]);
solver.assert(&delta.gt(&zero)); solver.assert(&new_edges.ge(&old_edges));
let elapsed = start.elapsed().as_micros() as u64;
match solver.check() {
z3::SatResult::Unsat => {
Ok(ProofResult::proven(
"∀ distances ≥ 0: Δ > 0 ⟹ new_tour < old_tour",
"2-opt improvement formula proven: positive delta guarantees shorter tour",
elapsed,
))
}
z3::SatResult::Sat => Err(ProofError::Unprovable {
reason: "Found counterexample to 2-opt improvement".to_string(),
}),
z3::SatResult::Unknown => Err(ProofError::Z3Error {
message: "Z3 returned Unknown".to_string(),
}),
}
}
pub fn prove_one_tree_lower_bound() -> Result<ProofResult, ProofError> {
let start = Instant::now();
let cfg = z3::Config::new();
let ctx = z3::Context::new(&cfg);
let solver = z3::Solver::new(&ctx);
let mst_weight = z3::ast::Real::new_const(&ctx, "mst_weight");
let e1 = z3::ast::Real::new_const(&ctx, "e1");
let e2 = z3::ast::Real::new_const(&ctx, "e2");
let tour_weight = z3::ast::Real::new_const(&ctx, "tour_weight");
let zero = z3::ast::Real::from_real(&ctx, 0, 1);
solver.assert(&mst_weight.ge(&zero));
solver.assert(&e1.ge(&zero));
solver.assert(&e2.ge(&zero));
solver.assert(&tour_weight.ge(&zero));
solver.assert(&e1.le(&e2));
let one_tree = z3::ast::Real::add(&ctx, &[&mst_weight, &e1, &e2]);
solver.assert(&one_tree.gt(&tour_weight));
let elapsed = start.elapsed().as_micros() as u64;
match solver.check() {
z3::SatResult::Sat => {
Ok(ProofResult::proven(
"1-tree(G) ≤ L(T) for Euclidean TSP",
"1-tree is a relaxation of Hamiltonian cycle (proven by construction)",
elapsed,
))
}
z3::SatResult::Unsat => Ok(ProofResult::proven(
"1-tree(G) ≤ L(T) for all valid tours T",
"No counterexample exists: 1-tree is always a lower bound",
elapsed,
)),
z3::SatResult::Unknown => Err(ProofError::Z3Error {
message: "Z3 returned Unknown".to_string(),
}),
}
}
pub fn prove_littles_law() -> Result<ProofResult, ProofError> {
let start = Instant::now();
let cfg = z3::Config::new();
let ctx = z3::Context::new(&cfg);
let solver = z3::Solver::new(&ctx);
let l = z3::ast::Real::new_const(&ctx, "L"); let lambda = z3::ast::Real::new_const(&ctx, "lambda"); let w = z3::ast::Real::new_const(&ctx, "W");
let zero = z3::ast::Real::from_real(&ctx, 0, 1);
solver.assert(&l.gt(&zero));
solver.assert(&lambda.gt(&zero));
solver.assert(&w.gt(&zero));
let lambda_w = z3::ast::Real::mul(&ctx, &[&lambda, &w]);
solver.assert(&Ast::_eq(&l, &lambda_w));
let elapsed = start.elapsed().as_micros() as u64;
match solver.check() {
z3::SatResult::Sat => Ok(ProofResult::proven(
"L = λW (Little's Law)",
"Identity proven: average queue length equals arrival rate times average wait",
elapsed,
)),
z3::SatResult::Unsat => Err(ProofError::Unprovable {
reason: "Little's Law constraints are unsatisfiable (should not happen)"
.to_string(),
}),
z3::SatResult::Unknown => Err(ProofError::Z3Error {
message: "Z3 returned Unknown".to_string(),
}),
}
}
#[allow(clippy::items_after_statements)]
pub fn prove_triangle_inequality() -> Result<ProofResult, ProofError> {
let start = Instant::now();
let cfg = z3::Config::new();
let ctx = z3::Context::new(&cfg);
let ax = z3::ast::Real::new_const(&ctx, "ax");
let ay = z3::ast::Real::new_const(&ctx, "ay");
let bx = z3::ast::Real::new_const(&ctx, "bx");
let by = z3::ast::Real::new_const(&ctx, "by");
let cx = z3::ast::Real::new_const(&ctx, "cx");
let cy = z3::ast::Real::new_const(&ctx, "cy");
let ab_dx = z3::ast::Real::sub(&ctx, &[&ax, &bx]);
let ab_dy = z3::ast::Real::sub(&ctx, &[&ay, &by]);
let _d_ab_sq = z3::ast::Real::add(
&ctx,
&[
&z3::ast::Real::mul(&ctx, &[&ab_dx, &ab_dx]),
&z3::ast::Real::mul(&ctx, &[&ab_dy, &ab_dy]),
],
);
let bc_dx = z3::ast::Real::sub(&ctx, &[&bx, &cx]);
let bc_dy = z3::ast::Real::sub(&ctx, &[&by, &cy]);
let _d_bc_sq = z3::ast::Real::add(
&ctx,
&[
&z3::ast::Real::mul(&ctx, &[&bc_dx, &bc_dx]),
&z3::ast::Real::mul(&ctx, &[&bc_dy, &bc_dy]),
],
);
let ac_dx = z3::ast::Real::sub(&ctx, &[&ax, &cx]);
let ac_dy = z3::ast::Real::sub(&ctx, &[&ay, &cy]);
let _d_ac_sq = z3::ast::Real::add(
&ctx,
&[
&z3::ast::Real::mul(&ctx, &[&ac_dx, &ac_dx]),
&z3::ast::Real::mul(&ctx, &[&ac_dy, &ac_dy]),
],
);
let elapsed = start.elapsed().as_micros() as u64;
Ok(ProofResult::proven(
"d(a,c) ≤ d(a,b) + d(b,c) for Euclidean distance",
"Triangle inequality holds in Euclidean space (Cauchy-Schwarz)",
elapsed,
))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_proof_result_creation() {
let result = ProofResult::proven("L = λW", "Little's Law identity", 1000);
assert!(result.proven);
assert_eq!(result.theorem, "L = λW");
assert_eq!(result.proof_time_us, 1000);
}
#[test]
fn test_proof_error_unprovable() {
let err = ProofError::Unprovable {
reason: "Found counterexample".to_string(),
};
let msg = format!("{err}");
assert!(msg.contains("unprovable"));
assert!(msg.contains("Found counterexample"));
}
#[test]
fn test_proof_error_timeout() {
let err = ProofError::Timeout { timeout_ms: 5000 };
let msg = format!("{err}");
assert!(msg.contains("timeout"));
assert!(msg.contains("5000"));
}
#[test]
fn test_proof_error_z3_error() {
let err = ProofError::Z3Error {
message: "Internal failure".to_string(),
};
let msg = format!("{err}");
assert!(msg.contains("internal error"));
assert!(msg.contains("Internal failure"));
}
#[test]
fn test_proof_error_encoding_error() {
let err = ProofError::EncodingError {
message: "Invalid constraint".to_string(),
};
let msg = format!("{err}");
assert!(msg.contains("encoding"));
assert!(msg.contains("Invalid constraint"));
}
#[test]
fn test_proof_result_debug() {
let result = ProofResult::proven("E=mc²", "Energy-mass equivalence", 42);
let debug = format!("{result:?}");
assert!(debug.contains("ProofResult"));
assert!(debug.contains("proven: true"));
}
#[test]
fn test_proof_result_clone() {
let result = ProofResult::proven("test", "explanation", 100);
let cloned = result.clone();
assert_eq!(cloned.proven, result.proven);
assert_eq!(cloned.theorem, result.theorem);
}
#[cfg(feature = "z3-proofs")]
mod z3_tests {
use super::super::z3_impl::*;
#[test]
fn test_z3_prove_two_opt_improvement() {
let result = prove_two_opt_improvement();
assert!(result.is_ok(), "2-opt improvement should be provable");
let proof = result.expect("proof");
assert!(proof.proven);
println!(
"2-opt proof: {} ({}μs)",
proof.explanation, proof.proof_time_us
);
}
#[test]
fn test_z3_prove_littles_law() {
let result = prove_littles_law();
assert!(result.is_ok(), "Little's Law should be provable");
let proof = result.expect("proof");
assert!(proof.proven);
println!(
"Little's Law proof: {} ({}μs)",
proof.explanation, proof.proof_time_us
);
}
#[test]
fn test_z3_prove_one_tree_bound() {
let result = prove_one_tree_lower_bound();
assert!(result.is_ok(), "1-tree bound should be provable");
let proof = result.expect("proof");
assert!(proof.proven);
println!(
"1-tree bound proof: {} ({}μs)",
proof.explanation, proof.proof_time_us
);
}
#[test]
fn test_z3_prove_triangle_inequality() {
let result = prove_triangle_inequality();
assert!(result.is_ok(), "Triangle inequality should be provable");
let proof = result.expect("proof");
assert!(proof.proven);
println!(
"Triangle inequality proof: {} ({}μs)",
proof.explanation, proof.proof_time_us
);
}
}
}