Spaces:
Running
Running
import os | |
import gradio as gr | |
from anthropic import Anthropic | |
from datetime import datetime, timedelta | |
from collections import deque | |
import random | |
import logging | |
import tempfile | |
from pathlib import Path | |
from sympy import * | |
import json | |
from pathlib import Path | |
# Set up logging | |
logging.basicConfig( | |
level=logging.DEBUG, | |
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' | |
) | |
logger = logging.getLogger(__name__) | |
# Initialize Anthropic client | |
anthropic = Anthropic( | |
api_key=os.environ.get('ANTHROPIC_API_KEY') | |
) | |
# Request tracking | |
MAX_REQUESTS_PER_DAY = 25 | |
request_history = deque(maxlen=1000) | |
SYMPY_GUIDELINES = """ | |
When writing SymPy code to verify solutions: | |
SYMPY_GUIDELINES = """ | |
When writing SymPy code to verify solutions: | |
1. Imports and Library Usage: | |
- Start with: from sympy import * | |
- DO NOT use NumPy, SciPy, or other libraries | |
- For numerical integration, use integrate() instead of scipy.integrate.quad() | |
- For any numerical computations, use native SymPy functions only | |
2. Variable Declaration and Functions: | |
- Always define symbolic variables first, for example: | |
```python | |
x, y, z = symbols('x y z') # For multiple variables | |
t = Symbol('t') # For single variables | |
``` | |
- Use Abs() for absolute value (not abs()) | |
- Define functions using Function() class when needed | |
3. Solving and Computing: | |
- Never use strings in solve() or other SymPy functions: | |
CORRECT: solve(eq, x) | |
INCORRECT: solve(eq, 'x') | |
- Define equations symbolically: | |
CORRECT: eq = 2*sqrt(h) - sqrt(12) + 5*k | |
INCORRECT: eq = 2*sqrt('h') - sqrt(12) + 5*k | |
4. Printing and Output: | |
- Include print statements for ALL calculations and results | |
- Print intermediate steps and final answers | |
- Print variable values after they are computed | |
- Use simple print statements instead of f-strings for SymPy expressions | |
- Print expressions with labels on separate lines: | |
```python | |
print("Expression label:") | |
print(expression) | |
``` | |
5. Numeric Calculations: | |
- Use Float() for decimal numbers in calculations | |
- Use float() for final printing of results | |
- Avoid evalf() as it may cause errors | |
- For numeric results: | |
```python | |
result = expression.evalf() | |
print("Result:") | |
print(float(result)) | |
``` | |
6. Working with Series and Sequences: | |
- Use Float() for sequence terms | |
- Convert sums to float() before printing | |
- For series calculations, print intermediate terms | |
""" | |
def load_proof_repository(): | |
"""Load the proof repository from the repository file""" | |
repo_path = Path("Lebl-theorems-all.json") | |
try: | |
with open(repo_path, "r") as f: | |
return json.load(f) | |
except Exception as e: | |
logger.error(f"Error loading proof repository: {str(e)}") | |
return None | |
TOPIC_MAPPINGS = { | |
"integration": ["integral", "integrable", "riemann", "integrate", "antiderivative"], | |
"continuity": ["continuous", "discontinuous", "discontinuity", "uniformly continuous"], | |
"sequences": ["sequence", "convergent", "divergent", "monotone", "subsequence"], | |
"series": ["series", "sum", "convergent series", "power series"], | |
"differentiation": ["derivative", "differentiable", "differential"], | |
"limits": ["limit", "cluster point", "accumulation"], | |
"functions": ["function", "mapping", "surjective", "injective", "bijective"], | |
"bounded": ["bound", "bounded above", "bounded below", "supremum", "infimum"] | |
} | |
def get_related_terms(topic): | |
"""Get all related terms for a given topic""" | |
# Get direct mappings | |
related = TOPIC_MAPPINGS.get(topic.lower(), []) | |
# Add the original topic | |
related.append(topic.lower()) | |
# Remove duplicates while preserving order | |
return list(dict.fromkeys(related)) | |
def matches_topic(text, topic_terms): | |
"""Check if any topic terms appear in the text""" | |
text_lower = text.lower() | |
return any(term in text_lower for term in topic_terms) | |
def get_relevant_proofs(topic): | |
"""Get relevant proofs from repository based on topic, randomly selecting examples""" | |
repository = load_proof_repository() | |
if not repository: | |
logger.error("Failed to load proof repository") | |
return [] | |
logger.debug(f"Searching for proofs related to topic: {topic}") | |
topic_terms = get_related_terms(topic) | |
logger.debug(f"Related terms: {topic_terms}") | |
relevant_proofs = [] | |
for theorem in repository.get("dataset", {}).get("theorems", []): | |
# Check categories | |
categories = theorem.get("categories", []) | |
category_match = any(matches_topic(cat, topic_terms) for cat in categories) | |
# Check contents | |
contents = theorem.get("contents", []) | |
content_match = any(matches_topic(content, topic_terms) for content in contents) | |
# Check title | |
title = theorem.get("title", "") | |
title_match = matches_topic(title, topic_terms) | |
if (category_match or content_match or title_match): | |
if theorem.get("contents") and theorem.get("proofs"): | |
proof_content = { | |
"title": theorem.get("title", ""), | |
"contents": theorem.get("contents", []), | |
"proofs": [p.get("contents", []) for p in theorem.get("proofs", [])] | |
} | |
relevant_proofs.append(proof_content) | |
logger.debug(f"Found matching proof: {proof_content['title']}") | |
logger.debug(f"Matched via: {'categories' if category_match else 'contents' if content_match else 'title'}") | |
logger.debug(f"Found {len(relevant_proofs)} relevant proofs before sampling") | |
# Randomly select 3 proofs if we have more than 3 | |
if len(relevant_proofs) > 3: | |
selected = random.sample(relevant_proofs, 3) | |
logger.debug("Selected proofs for enhancement:") | |
for proof in selected: | |
logger.debug(f"- {proof['title']}") | |
return selected | |
return relevant_proofs | |
def enhance_prompt_with_proofs(system_prompt, subject, topic): | |
"""Enhance the system prompt with relevant proofs if subject is Real Analysis""" | |
if subject != "Real Analysis": | |
logger.debug("Skipping proof enhancement - not Real Analysis") | |
return system_prompt | |
relevant_proofs = get_relevant_proofs(topic) | |
if not relevant_proofs: | |
logger.debug(f"No relevant proofs found for topic: {topic}") | |
return system_prompt | |
logger.debug(f"Enhancing prompt with {len(relevant_proofs)} proofs") | |
# Add proof examples to the prompt | |
proof_examples = "\n\nReference these proof examples for style and approach:\n" | |
for proof in relevant_proofs: | |
logger.debug(f"Adding proof: {proof['title']}") | |
proof_examples += f"\nTheorem: {proof['title']}\n" | |
proof_examples += "Statement: " + " ".join(proof['contents']) + "\n" | |
if proof['proofs']: | |
first_proof = " ".join(proof['proofs'][0]) | |
logger.debug(f"Proof length: {len(first_proof)} characters") | |
proof_examples += "Proof: " + first_proof + "\n" | |
# Add specific instructions for using the examples | |
enhanced_prompt = f"""{system_prompt} | |
ADDITIONAL PROOF GUIDELINES: | |
1. Consider the following proof examples from established textbooks | |
2. Maintain similar level of rigor and detail | |
3. Use similar proof techniques where applicable | |
4. Follow similar notation and presentation style | |
{proof_examples}""" | |
return enhanced_prompt | |
def get_difficulty_parameters(difficulty_level): | |
"""Return specific parameters and constraints based on difficulty level""" | |
parameters = { | |
1: { # Very Easy | |
"description": "very easy, suitable for beginners", | |
"constraints": [ | |
"Use only basic concepts and straightforward calculations", | |
"Break complex problems into smaller, guided steps", | |
"Provide hints within the question when needed", | |
"Use simple numbers and avoid complex algebraic expressions" | |
], | |
"example_style": "Similar to standard homework problems", | |
"model": "claude-3-5-sonnet-20241022" | |
}, | |
2: { # Easy | |
"description": "easy, but requiring some thought", | |
"constraints": [ | |
"Use basic concepts with minor complications", | |
"Include two-step problems", | |
"Minimal guidance provided", | |
"Use moderately complex numbers or expressions" | |
], | |
"example_style": "Similar to quiz questions", | |
"model": "claude-3-5-sonnet-20241022" | |
}, | |
3: { # Intermediate | |
"description": "intermediate difficulty, testing deeper understanding", | |
"constraints": [ | |
"Combine 2-3 related concepts", | |
"Include some non-obvious solution paths", | |
"Require multi-step reasoning", | |
"Use moderate algebraic complexity" | |
], | |
"example_style": "Similar to midterm exam questions", | |
"model": "claude-3-5-sonnet-20241022" | |
}, | |
4: { # Difficult | |
"description": "challenging, requiring strong mathematical maturity", | |
"constraints": [ | |
"Combine multiple concepts creatively", | |
"Require insight and deep understanding", | |
"Include non-standard approaches", | |
"Use sophisticated mathematical reasoning" | |
], | |
"example_style": "Similar to final exam questions", | |
"model": "claude-3-5-sonnet-20241022" | |
}, | |
5: { # Very Difficult | |
"description": "very challenging, testing mastery and creativity at a graduate level", | |
"constraints": [ | |
"Create novel applications of theoretical concepts", | |
"Require graduate-level mathematical reasoning", | |
"Combine multiple advanced topics in unexpected ways", | |
"Demand creative problem-solving approaches", | |
"Include rigorous proof construction", | |
"Require synthesis across mathematical domains", | |
"Test deep theoretical understanding" | |
], | |
"example_style": "Similar to graduate qualifying exams or advanced competition problems", | |
"model": "claude-3-5-sonnet-20241022" | |
} | |
} | |
return parameters.get(difficulty_level) | |
def create_latex_document(content, questions_only=False): | |
"""Create a complete LaTeX document""" | |
try: | |
latex_header = r"""\documentclass{article} | |
\usepackage{amsmath,amssymb} | |
\usepackage[margin=1in]{geometry} | |
\begin{document} | |
\title{Mathematics Question} | |
\maketitle | |
""" | |
latex_footer = r"\end{document}" | |
if questions_only: | |
# Modified to handle single question | |
processed_content = content.split('Solution:')[0] | |
content = processed_content | |
full_document = f"{latex_header}\n{content}\n{latex_footer}" | |
logger.debug(f"Created {'questions-only' if questions_only else 'full'} LaTeX document") | |
return full_document | |
except Exception as e: | |
logger.error(f"Error creating LaTeX document: {str(e)}") | |
raise | |
def save_to_temp_file(content, filename): | |
"""Save content to a temporary file and return the path""" | |
try: | |
temp_dir = Path(tempfile.gettempdir()) / "math_test_files" | |
temp_dir.mkdir(exist_ok=True) | |
file_path = temp_dir / filename | |
file_path.write_text(content, encoding='utf-8') | |
logger.debug(f"Saved content to temporary file: {file_path}") | |
return str(file_path) | |
except Exception as e: | |
logger.error(f"Error saving temporary file: {str(e)}") | |
raise | |
def get_problem_type_addition(question_type): | |
"""Return specific requirements based on problem type""" | |
problem_type_additions = { | |
"application": """ | |
The application question MUST: | |
- Present a real-world scenario or practical problem | |
- Require modeling the situation mathematically | |
- Connect abstract mathematical concepts to concrete situations | |
- Include realistic context and data | |
- Require students to: | |
1. Identify relevant mathematical concepts | |
2. Translate the practical problem into mathematical terms | |
3. Solve using appropriate mathematical techniques | |
4. Interpret the results in the context of the original problem | |
Example contexts might include: | |
- Physics applications (motion, forces, work) | |
- Engineering scenarios (optimization, rates of change) | |
- Economics problems (cost optimization, growth models) | |
- Biological systems (population growth, reaction rates) | |
- Business applications (profit maximization, inventory management) | |
- Social science applications (demographic models, social network analysis) | |
- Data science applications (regression, statistical analysis) | |
""", | |
"proof": """ | |
The proof question MUST: | |
- Require a formal mathematical proof | |
- Focus on demonstrating logical reasoning | |
- Require justification for each step | |
- Emphasize theoretical understanding | |
The proof question MAY NOT: | |
- Include Real-world applications or scenarios | |
- Include Pure computation problems | |
- Ask only for numerical answers | |
""", | |
"computation": """ | |
The computation question MUST: | |
- Require specific algebraic calculations | |
- Focus on mathematical techniques | |
- Have concrete answers in the form of algebraic expressions (about half of questions) or numbers (about half of questions) | |
- Test procedural knowledge | |
The computation question MAY NOT: | |
- Include extended real-world applications or scenarios | |
- Ask for a proof | |
""" | |
} | |
return problem_type_additions.get(question_type, "") | |
def generate_question(subject, difficulty, question_type): | |
"""Generate a single math question""" | |
try: | |
if not os.environ.get('ANTHROPIC_API_KEY'): | |
logger.error("Anthropic API key not found") | |
return "Error: Anthropic API key not configured", None, None | |
logger.debug(f"Generating {question_type} question for subject: {subject} at difficulty level: {difficulty}") | |
# Check rate limit | |
now = datetime.now() | |
while request_history and (now - request_history[0]) > timedelta(days=1): | |
request_history.popleft() | |
if len(request_history) >= MAX_REQUESTS_PER_DAY: | |
return "Daily request limit reached. Please try again tomorrow.", None, None | |
request_history.append(now) | |
topics = { | |
"Single Variable Calculus": ["limits", "derivatives", "integrals", "series", "applications"], | |
"Multivariable Calculus": ["partial derivatives", "multiple integrals", "vector fields", "optimization"], | |
"Linear Algebra": ["matrices", "vector spaces", "eigenvalues", "linear transformations"], | |
"Differential Equations": ["first order equations", "second order equations", "systems", "stability analysis"], | |
"Real Analysis": ["sequences", "series", "continuity", "differentiation", "integration"], | |
"Complex Analysis": ["complex functions", "analyticity", "contour integration", "residues"], | |
"Abstract Algebra": ["groups", "rings", "fields", "homomorphisms"], | |
"Probability Theory": ["probability spaces", "random variables", "distributions", "limit theorems"], | |
"Numerical Analysis": ["approximation", "interpolation", "numerical integration", "error analysis"], | |
"Topology": ["metric spaces", "continuity", "compactness", "connectedness"] | |
} | |
selected_topic = random.choice(topics.get(subject, ["general"])) | |
logger.debug(f"Selected topic: {selected_topic}") | |
difficulty_params = get_difficulty_parameters(difficulty) | |
problem_type_addition = get_problem_type_addition(question_type) | |
system_prompt = f"""You are an expert mathematics professor creating a {difficulty_params['description']} exam question. | |
STRICT REQUIREMENTS: | |
1. Write exactly 1 {question_type} question on {subject} covering {selected_topic}. | |
2. Difficulty Level Guidelines: | |
{difficulty_params['description'].upper()} | |
Follow these specific constraints: | |
{chr(10).join(f' - {c}' for c in difficulty_params['constraints'])} | |
{problem_type_addition} | |
3. Style Reference: | |
Question should be {difficulty_params['example_style']} | |
4. For LaTeX formatting: | |
- Make sure that the question statement uses proper LaTeX math mode | |
- Use $ for inline math | |
- Use $$ on separate lines for equations and solutions | |
- Put each solution step on its own line in $$ $$ | |
- DO NOT use \\begin{{aligned}} or similar environments | |
5. Include a detailed solution | |
- If the question involves geometry make sure to identify any general geometric formulas that apply, For example: | |
* Areas/volumes of common shapes and solids | |
* Cross-sectional areas of geometric figures | |
* Arc lengths and sector areas | |
- When setting up differential equations either in calculus or differential equation applications | |
* carefully consider the direction of change in variables | |
* ensure integration bounds align with the physical direction of the process being modeled | |
6. Maintain clear formatting | |
7. At the end of the LaTeX solution output, print SymPy code that you would use to solve or verify the main equations in the question. | |
8. Observe the folloiwng SymPy Guidelines | |
{SYMPY_GUIDELINES}""" | |
#Consider | |
#When writing SymPy code: | |
#- Use FiniteSet(1, 2, 3) instead of Set([1, 2, 3]) for finite sets | |
#- Import specific functions instead of using 'from sympy import *' | |
#- Print results of each calculation step | |
# Enhance the prompt with proof examples if applicable | |
if subject == "Real Analysis" and question_type == "proof": | |
system_prompt = enhance_prompt_with_proofs(system_prompt, subject, selected_topic) | |
logger.debug("Sending request to Anthropic API") | |
message = anthropic.messages.create( | |
model=difficulty_params['model'], | |
max_tokens=4096, | |
temperature=0.7, | |
messages=[{ | |
"role": "user", | |
"content": f"{system_prompt}\n\nWrite a question for {subject}." | |
}] | |
) | |
if not hasattr(message, 'content') or not message.content: | |
logger.error("No content received from Anthropic API") | |
return "Error: No content received from API", None, None | |
response_text = message.content[0].text | |
logger.debug("Successfully received response from Anthropic API") | |
# Execute SymPy code and append results | |
sympy_output = extract_and_run_sympy_code_simple(response_text) | |
if sympy_output: | |
# Check if SymPy ran successfully | |
if "Error" not in sympy_output: | |
resolution = check_and_resolve_discrepancy(response_text, sympy_output) | |
response_text = f"{response_text}\n\nSymPy Verification Results:\n```\n{sympy_output}\n```\n\nVerification Analysis:\n{resolution}" | |
else: | |
# Just append SymPy results if there was an error | |
response_text += f"\n\nSymPy Verification Results:\n```\n{sympy_output}\n```" | |
# Create LaTeX content | |
questions_latex = create_latex_document(response_text, questions_only=True) | |
full_latex = create_latex_document(response_text, questions_only=False) | |
# Save to temporary files | |
questions_path = save_to_temp_file(questions_latex, "question.tex") | |
full_path = save_to_temp_file(full_latex, "full_question.tex") | |
logger.debug("Successfully created temporary files") | |
return response_text, questions_path, full_path | |
except Exception as e: | |
logger.error(f"Error generating question: {str(e)}") | |
return f"Error: {str(e)}", None, Non | |
def extract_and_run_sympy_code_simple(response_text): | |
""" | |
Extract SymPy code from the response and execute it. | |
Handles SymPy expression printing safely and ensures proper symbol definition. | |
""" | |
try: | |
# Extract code | |
sympy_start = response_text.find('```python') | |
if sympy_start == -1: | |
return "No SymPy code found in the response." | |
code_start = response_text.find('\n', sympy_start) + 1 | |
code_end = response_text.find('```', code_start) | |
if code_end == -1: | |
return "Malformed SymPy code block." | |
sympy_code = response_text[code_start:code_end].strip() | |
# Add wrapper function to ensure proper symbol definitions | |
wrapper_code = """ | |
def run_sympy_verification(): | |
# Import SymPy | |
from sympy import * | |
# Define standard helper functions | |
def safe_print_expr(label, expr): | |
print(label) | |
if expr is not None: | |
try: | |
float_val = float(expr) | |
print(float_val) | |
except: | |
print(expr) | |
# Start user code | |
""" | |
# Indent the original code | |
indented_code = '\n'.join(' ' + line for line in sympy_code.split('\n')) | |
# Add the execution call | |
full_code = wrapper_code + indented_code + "\n\n# Execute verification\nrun_sympy_verification()" | |
# Set up basic SymPy environment | |
import io | |
import sympy | |
from contextlib import redirect_stdout | |
# Create a dictionary of allowed names | |
globals_dict = { | |
"sympy": sympy, | |
"Symbol": sympy.Symbol, | |
"solve": sympy.solve, | |
"sqrt": sympy.sqrt, | |
"pi": sympy.pi, | |
"diff": sympy.diff, | |
"integrate": sympy.integrate, | |
"simplify": sympy.simplify, | |
"print": print, | |
"float": float | |
} | |
# Capture output | |
output_buffer = io.StringIO() | |
with redirect_stdout(output_buffer): | |
exec(full_code, globals_dict) | |
return output_buffer.getvalue().strip() or "No output produced" | |
except Exception as e: | |
return f"Error executing SymPy code: {str(e)}" | |
def check_and_resolve_discrepancy(initial_response, sympy_output): | |
""" | |
Compare the SymPy output with the initial response and resolve any discrepancies | |
by making another API call to Claude. | |
""" | |
try: | |
resolution_prompt = f"""Here is a mathematics question with two answers. | |
The first, called Original solution, is a complete solution. | |
The second, called SymPy Verification, will only provide the final answer. | |
If the SymPy Verification answer is consistent with the final answer Original solution, | |
then please say that they are consistent and briefly explain why. | |
If the two answers are inconsistent with each other then please: | |
1. Identify which solution is correct | |
2. Explain the error in the incorrect solution | |
3. Provide a revised complete solution that fixes any errors | |
Original solution: | |
{initial_response} | |
SymPy Verification Results: | |
{sympy_output} | |
Please maintain the same LaTeX formatting as the original solution.""" | |
# Make API call for resolution | |
message = anthropic.messages.create( | |
model="claude-3-5-sonnet-20241022", | |
max_tokens=4096, | |
temperature=0.2, | |
messages=[{ | |
"role": "user", | |
"content": resolution_prompt | |
}] | |
) | |
resolution_text = message.content[0].text | |
# Check if resolution contains new SymPy code | |
if "```python" in resolution_text: | |
new_sympy_output = extract_and_run_sympy_code(resolution_text) | |
resolution_text += "\n\nNew SymPy Verification Results:\n```\n" + new_sympy_output + "\n```" | |
return resolution_text | |
except Exception as e: | |
logger.error(f"Error in discrepancy resolution: {str(e)}") | |
return initial_response | |
# Create Gradio interface | |
with gr.Blocks() as interface: | |
gr.Markdown("# Advanced Mathematics Question Generator") | |
gr.Markdown("""Generates a unique university-level mathematics question with solution using Claude 3. | |
Each question features different topics and difficulty levels. Limited to 25 requests per day.""") | |
with gr.Row(): | |
with gr.Column(): | |
subject_dropdown = gr.Dropdown( | |
choices=[ | |
"Single Variable Calculus", | |
"Multivariable Calculus", | |
"Linear Algebra", | |
"Differential Equations", | |
"Real Analysis", | |
"Complex Analysis", | |
"Abstract Algebra", | |
"Probability Theory", | |
"Numerical Analysis", | |
"Topology" | |
], | |
label="Select Mathematics Subject", | |
info="Choose a subject for the question" | |
) | |
difficulty_slider = gr.Slider( | |
minimum=1, | |
maximum=5, | |
step=1, | |
value=3, | |
label="Difficulty Level", | |
info="1: Very Easy, 2: Easy, 3: Moderate, 4: Difficult, 5: Very Difficult" | |
) | |
question_type = gr.Radio( | |
choices=["computation", "proof", "application"], | |
label="Question Type", | |
info="Select the type of question you want", | |
value="computation" | |
) | |
generate_btn = gr.Button("Generate Question") | |
output_text = gr.Markdown( | |
label="Generated Question Preview", | |
latex_delimiters=[ | |
{"left": "$$", "right": "$$", "display": True}, | |
{"left": "$", "right": "$", "display": False} | |
] | |
) | |
with gr.Row(): | |
questions_file = gr.File(label="Question Only (LaTeX)") | |
full_file = gr.File(label="Question with Solution (LaTeX)") | |
generate_btn.click( | |
generate_question, | |
inputs=[ | |
subject_dropdown, | |
difficulty_slider, | |
question_type | |
], | |
outputs=[output_text, questions_file, full_file] | |
) | |
if __name__ == "__main__": | |
logger.info("Starting application") | |
interface.launch() |