import sympy as sp import random from typing import Dict, Any, Tuple class TaskGenerationEngine: def __init__(self): self.x = sp.Symbol('x') # Components for generating random functions F(x) self.basic_functions = [ lambda x, c: x**c, lambda x, c: sp.sin(c*x), lambda x, c: sp.cos(c*x), lambda x, c: sp.exp(c*x), lambda x, c: sp.ln(sp.Abs(c*x)) ] def _score_difficulty(self, components: int, nesting: int) -> float: """D = num_components + degree_of_nesting * 2""" return float(components + nesting * 2.0) def generate_random_function(self, complexity: int) -> Tuple[Any, float]: """Generates a random F(x).""" num_components = max(1, int(complexity / 2)) nesting = max(0, int(complexity / 4)) f_expr = 0 for _ in range(num_components): comp_func = random.choice(self.basic_functions) coeff = random.randint(1, 5) term = comp_func(self.x, coeff) # Apply nesting for _ in range(nesting): outer = random.choice(self.basic_functions) term = outer(term, 1) f_expr += random.randint(1, 10) * term return f_expr, self._score_difficulty(num_components, nesting) def generate_task(self, target_difficulty_band: float) -> Dict[str, Any]: """Provides an indefinite integral task.""" complexity = max(1, int(target_difficulty_band)) # 1. Generate F(x) F_expr, diff = self.generate_random_function(complexity) # 2. Differentiate to get the problem f(x) f_expr = sp.diff(F_expr, self.x) # 3. Format strings problem_text = f"Find the indefinite integral: \int ({sp.pretty(f_expr)}) dx" solution_text = f"{sp.simplify(F_expr)} + C" return { "problem": problem_text, "difficulty": diff, "solution": solution_text, "type": "integration", "sympy_F": F_expr, "sympy_f": f_expr } def generate_variants(self, task: Dict[str, Any], count: int = 2) -> list[Dict[str, Any]]: """ LADDER Component: Recursive Decomposition for Integration. Breaks down sums or simplifies coefficients. """ variants = [] F_expr = task.get("sympy_F") if F_expr is None: # Fallback if task was not generated by us return [self.generate_task(max(1, task.get("difficulty", 2) - 2))] # Recursive Rule 1: Linearity (split sums) if isinstance(F_expr, sp.Add): args = F_expr.args for arg in args[:count]: sub_F = arg sub_f = sp.diff(sub_F, self.x) variants.append({ "problem": f"Integrate step-variant: \int ({sp.pretty(sub_f)}) dx", "solution": f"{sub_F} + C", "difficulty": task["difficulty"] - 1.0, "type": "integration", "sympy_F": sub_F, "sympy_f": sub_f }) # Recursive Rule 2: Constant simplification if not variants: # Just return a simpler integral by reducing difficulty variants.append(self.generate_task(max(1.0, task["difficulty"] - 2.0))) return variants[:count]