"""
Verification helpers for math competition answers.
This module is designed to be copy-pasted into Jupyter notebooks.
"""
from fractions import Fraction
from typing import Any, Optional, Tuple, Union
# Try sympy for symbolic verification
try:
from sympy import simplify, parse_expr, N, Eq
SYMPY_AVAILABLE = True
except ImportError:
SYMPY_AVAILABLE = False
# =============================================================================
# BASIC VERIFICATION CHECKS
# =============================================================================
def is_integer(value) -> bool:
"""Check if value is an integer."""
if isinstance(value, int):
return True
if isinstance(value, float):
return value.is_integer()
if isinstance(value, Fraction):
return value.denominator == 1
if isinstance(value, str):
try:
int(value)
return True
except ValueError:
return False
# For sympy
if hasattr(value, 'is_Integer'):
return bool(value.is_Integer)
return False
def is_positive(value) -> bool:
"""Check if value is positive."""
try:
return float(value) > 0
except:
if hasattr(value, 'is_positive'):
return bool(value.is_positive)
return False
def is_in_range(value, lo: float, hi: float) -> bool:
"""Check if lo <= value <= hi."""
try:
v = float(value)
return lo <= v <= hi
except:
return False
def check_mod(value, modulus: int, expected: Optional[int] = None) -> Union[int, bool]:
"""
Check or compute value mod modulus.
If expected is None, returns value % modulus.
If expected is given, returns True if value ≡ expected (mod modulus).
"""
try:
v = int(value)
result = v % modulus
if expected is None:
return result
return result == (expected % modulus)
except:
return False
def check_divisible(value, divisor: int) -> bool:
"""Check if value is divisible by divisor."""
try:
return int(value) % divisor == 0
except:
return False
def check_parity(value, expected: str) -> bool:
"""
Check parity.
Args:
value: Number to check
expected: 'even' or 'odd'
"""
try:
v = int(value)
if expected == 'even':
return v % 2 == 0
elif expected == 'odd':
return v % 2 == 1
else:
raise ValueError(f"expected must be 'even' or 'odd', got {expected}")
except (ValueError, TypeError):
return False
# =============================================================================
# ADVANCED VERIFICATION
# =============================================================================
def verify_equation(value, equation_str: str, var: str = 'x', tolerance: float = 1e-9) -> bool:
"""
Verify that value satisfies an equation.
Args:
value: The candidate solution
equation_str: Equation like "x^2 - 4 = 0" or "x^2 = 4"
var: Variable name in the equation
tolerance: For numeric comparison
Returns:
True if value satisfies the equation
"""
if not SYMPY_AVAILABLE:
raise ImportError("sympy required for equation verification")
from sympy import symbols
x = symbols(var)
if '=' in equation_str:
lhs, rhs = equation_str.split('=')
expr = parse_expr(lhs) - parse_expr(rhs)
else:
expr = parse_expr(equation_str)
# Substitute value
result = expr.subs(x, value)
result = simplify(result)
# Check if zero
if result == 0:
return True
# Numeric check with tolerance
try:
numeric = float(N(result))
return abs(numeric) <= tolerance
except:
return False
def verify_identity(lhs_str: str, rhs_str: str) -> bool:
"""
Verify that two expressions are symbolically equal.
Args:
lhs_str: Left-hand side expression
rhs_str: Right-hand side expression
Returns:
True if expressions are equivalent
"""
if not SYMPY_AVAILABLE:
raise ImportError("sympy required for identity verification")
lhs = parse_expr(lhs_str)
rhs = parse_expr(rhs_str)
diff = simplify(lhs - rhs)
return diff == 0
def verify_numeric(computed, expected, tolerance: float = 1e-9) -> bool:
"""
Verify two values are numerically equal within tolerance.
"""
try:
c = float(computed)
e = float(expected)
return abs(c - e) <= tolerance
except:
return False
# =============================================================================
# COMPOSITE VERIFICATION
# =============================================================================
def verify_answer(
value,
checks: dict
) -> Tuple[bool, str]:
"""
Run multiple verification checks on an answer.
Args:
value: The answer to verify
checks: Dict of checks to run, e.g.:
{
'integer': True,
'positive': True,
'range': (0, 1000),
'mod': {'modulus': 7, 'expected': 3},
'divisible': 12,
'parity': 'even',
'equation': 'x^2 - 16 = 0'
}
Returns:
(passed: bool, message: str)
"""
results = []
if 'integer' in checks and checks['integer']:
ok = is_integer(value)
results.append(('integer', ok))
if not ok:
return False, f"Failed: expected integer, got {value}"
if 'positive' in checks and checks['positive']:
ok = is_positive(value)
results.append(('positive', ok))
if not ok:
return False, f"Failed: expected positive, got {value}"
if 'range' in checks:
lo, hi = checks['range']
ok = is_in_range(value, lo, hi)
results.append(('range', ok))
if not ok:
return False, f"Failed: {value} not in range [{lo}, {hi}]"
if 'mod' in checks:
mod_check = checks['mod']
modulus = mod_check['modulus']
expected = mod_check.get('expected')
if expected is not None:
ok = check_mod(value, modulus, expected)
results.append(('mod', ok))
if not ok:
actual = int(value) % modulus
return False, f"Failed: {value} ≡ {actual} (mod {modulus}), expected {expected}"
if 'divisible' in checks:
divisor = checks['divisible']
ok = check_divisible(value, divisor)
results.append(('divisible', ok))
if not ok:
return False, f"Failed: {value} not divisible by {divisor}"
if 'parity' in checks:
parity = checks['parity']
ok = check_parity(value, parity)
results.append(('parity', ok))
if not ok:
return False, f"Failed: {value} is not {parity}"
if 'equation' in checks:
eq = checks['equation']
ok = verify_equation(value, eq)
results.append(('equation', ok))
if not ok:
return False, f"Failed: {value} does not satisfy {eq}"
passed_count = sum(1 for _, ok in results if ok)
return True, f"Passed {passed_count}/{len(results)} checks"
# =============================================================================
# SANITY CHECKS FOR COMPETITION
# =============================================================================
def sanity_check(value, problem_type: str = 'general') -> Tuple[bool, str]:
"""
Run basic sanity checks based on problem type.
Args:
value: The computed answer
problem_type: 'counting', 'probability', 'geometry', 'number_theory', 'general'
Returns:
(passed: bool, message: str)
"""
warnings = []
# Basic checks
if value is None:
return False, "Answer is None"
try:
v = float(value)
except:
return False, f"Cannot convert {value} to number"
# Type-specific checks
if problem_type == 'counting':
if not is_integer(value):
return False, f"Counting answer must be integer, got {value}"
if v < 0:
return False, f"Counting answer must be non-negative, got {value}"
if v == 0:
warnings.append("Answer is 0 - verify this is correct")
elif problem_type == 'probability':
if v < 0 or v > 1:
return False, f"Probability must be in [0, 1], got {value}"
elif problem_type == 'geometry':
if v < 0:
warnings.append("Geometric quantity is negative")
elif problem_type == 'number_theory':
if not is_integer(value):
warnings.append("Number theory answer is not an integer")
# Common red flags
if abs(v) > 10**15:
warnings.append(f"Very large answer: {value}")
if isinstance(value, float) and not value.is_integer():
warnings.append("Answer is a non-integer float - consider exact arithmetic")
if warnings:
return True, "Passed with warnings: " + "; ".join(warnings)
return True, "Sanity checks passed"