mirror of
https://github.com/NousResearch/atropos.git
synced 2026-04-19 12:57:58 +00:00
378 lines
13 KiB
Python
378 lines
13 KiB
Python
import random
|
|
from typing import Any, Dict, List, Optional, Tuple
|
|
|
|
import mathgenerator
|
|
|
|
|
|
class MathCurriculum:
|
|
"""
|
|
A curriculum manager for the mathgenerator library.
|
|
|
|
This class organizes math problems by difficulty and provides methods
|
|
to generate problems of appropriate difficulty based on the learner's
|
|
performance.
|
|
"""
|
|
|
|
# Define difficulty levels and map generator IDs to each level
|
|
DIFFICULTY_LEVELS = {
|
|
# Level 1: Basic arithmetic operations
|
|
1: [
|
|
0, # Addition
|
|
1, # Subtraction
|
|
2, # Multiplication
|
|
3, # Division
|
|
8, # Square
|
|
31, # Factorial
|
|
71, # Absolute difference between two numbers
|
|
80, # Percentage of a number
|
|
90, # isprime
|
|
],
|
|
# Level 2: Basic operations with fractions and pre-algebra
|
|
2: [
|
|
6, # Square Root
|
|
11, # Basic Algebra
|
|
13, # Fraction to Decimal
|
|
16, # Fraction Division
|
|
28, # Fraction Multiplication
|
|
44, # Compare Fractions
|
|
47, # Cube Root
|
|
53, # Exponentiation
|
|
97, # Power of Powers
|
|
118, # Percentage difference
|
|
119, # Percentage error
|
|
124, # Is Composite
|
|
],
|
|
# Level 3: Basic geometry and more algebra
|
|
3: [
|
|
18, # Area of Triangle
|
|
19, # Triangle exists check
|
|
22, # Third Angle of Triangle
|
|
24, # Distance between 2 points
|
|
25, # Pythagorean Theorem
|
|
49, # Fourth Angle of Quadrilateral
|
|
58, # Sum of Angles of Polygon
|
|
75, # Area of a Sector
|
|
96, # Perimeter of Polygons
|
|
104, # Circumference
|
|
108, # Arc length of Angle
|
|
112, # Area of Circle
|
|
115, # Area of Circle given center and a point on circle
|
|
],
|
|
# Level 4: More advanced algebra and basic statistics
|
|
4: [
|
|
9, # LCM (Least Common Multiple)
|
|
10, # GCD (Greatest Common Denominator)
|
|
20, # Midpoint of the two point
|
|
21, # Factoring Quadratic
|
|
23, # Solve a System of Equations in R^2
|
|
26, # Linear Equations
|
|
40, # Common Factors
|
|
41, # Intersection of Two Lines
|
|
45, # Simple Interest
|
|
50, # Quadratic Equation
|
|
76, # Mean and Median
|
|
78, # Compound Interest
|
|
105, # Combine Like terms
|
|
],
|
|
# Level 5: Vectors, matrices, and solid geometry
|
|
5: [
|
|
17, # Integer Multiplication with 2x2 Matrix
|
|
32, # Surface Area of Cube
|
|
33, # Surface Area of Cuboid
|
|
34, # Surface Area of Cylinder
|
|
35, # Volume of Cube
|
|
36, # Volume of Cuboid
|
|
37, # Volume of cylinder
|
|
38, # Surface Area of cone
|
|
39, # Volume of cone
|
|
43, # Cross Product of 2 Vectors
|
|
46, # Multiplication of two matrices
|
|
60, # Surface Area of Sphere
|
|
61, # Volume of Sphere
|
|
70, # Angle between 2 vectors
|
|
72, # Dot Product of 2 Vectors
|
|
77, # Determinant to 2x2 Matrix
|
|
95, # Curved surface area of a cylinder
|
|
113, # Volume of frustum
|
|
117, # Volume of Hemisphere
|
|
122, # Volume of pyramid
|
|
123, # Surface area of pyramid
|
|
],
|
|
# Level 6: Advanced topics (calculus, statistics, computer science)
|
|
6: [
|
|
4, # Binary Complement 1s
|
|
5, # Modulo Division
|
|
7, # Power Rule Differentiation
|
|
12, # Logarithm
|
|
14, # Decimal to Binary
|
|
15, # Binary to Decimal
|
|
27, # Prime Factorisation
|
|
30, # Combinations of Objects
|
|
42, # Permutations
|
|
48, # Power Rule Integration
|
|
52, # Probability of a certain sum appearing on faces of dice
|
|
54, # Confidence interval For sample S
|
|
55, # Comparing surds
|
|
56, # Fibonacci Series
|
|
59, # Mean,Standard Deviation,Variance
|
|
62, # nth Fibonacci number
|
|
64, # Binary to Hexadecimal
|
|
73, # Binary 2's Complement
|
|
79, # Decimal to Hexadecimal
|
|
84, # Converts decimal to octal
|
|
88, # Trigonometric Differentiation
|
|
89, # Definite Integral of Quadratic Equation
|
|
91, # Binary Coded Decimal to Integer
|
|
103, # Decimal to Binary Coded Decimal
|
|
107, # Conditional Probability
|
|
110, # Stationary Points
|
|
],
|
|
# Level 7: Most complex topics
|
|
7: [
|
|
65, # Multiplication of 2 complex numbers
|
|
66, # Geometric Progression
|
|
67, # Geometric Mean of N Numbers
|
|
68, # Harmonic Mean of N Numbers
|
|
69, # Euclidean norm or L2 norm of a vector
|
|
74, # Inverse of a Matrix
|
|
85, # Converts decimal to Roman Numerals
|
|
92, # Complex To Polar Form
|
|
93, # Union,Intersection,Difference of Two Sets
|
|
94, # Base Conversion
|
|
98, # Quotient of Powers with Same Base
|
|
99, # Quotient of Powers with Same Power
|
|
100, # complex Quadratic Equation
|
|
101, # Leap Year or Not
|
|
106, # signum function
|
|
109, # Binomial distribution
|
|
111, # Expanding Factored Binomial
|
|
121, # Product of scientific notations
|
|
],
|
|
}
|
|
|
|
def __init__(
|
|
self,
|
|
starting_level: int = 1,
|
|
progress_threshold: float = 0.8,
|
|
min_evaluations: int = 5,
|
|
):
|
|
"""
|
|
Initialize the curriculum manager.
|
|
|
|
Args:
|
|
starting_level: The difficulty level to start with (default: 1)
|
|
progress_threshold: The success rate required to advance to the next level (default: 0.8)
|
|
min_evaluations: Minimum number of evaluations needed before considering level advancement (default: 5)
|
|
"""
|
|
self.current_level = starting_level
|
|
self.progress_threshold = progress_threshold
|
|
self.min_evaluations = min_evaluations
|
|
|
|
# Performance tracking
|
|
self.performance_history = {
|
|
level: [] for level in self.DIFFICULTY_LEVELS.keys()
|
|
}
|
|
|
|
# Ensure starting level is valid
|
|
if starting_level not in self.DIFFICULTY_LEVELS:
|
|
raise ValueError(
|
|
f"Invalid starting level: {starting_level}. Available levels: {list(self.DIFFICULTY_LEVELS.keys())}"
|
|
)
|
|
|
|
def get_problem(self) -> Tuple[str, str, int]:
|
|
"""
|
|
Generate a math problem at the current difficulty level.
|
|
|
|
Returns:
|
|
Tuple containing (problem_text, solution_text, generator_id)
|
|
"""
|
|
# Get the available generator IDs for the current level
|
|
available_generators = self.DIFFICULTY_LEVELS[self.current_level]
|
|
|
|
# Try generators until one works
|
|
max_attempts = 5 # Limit the number of attempts to avoid infinite loops
|
|
attempts = 0
|
|
|
|
while attempts < max_attempts:
|
|
# Get a random generator ID from the current level
|
|
generator_id = random.choice(available_generators)
|
|
|
|
try:
|
|
# Generate the problem
|
|
problem, solution = mathgenerator.genById(generator_id)
|
|
return problem, solution, generator_id
|
|
except Exception as e:
|
|
# Log the error and try another generator
|
|
print(f"Error with generator {generator_id}: {str(e)}")
|
|
attempts += 1
|
|
|
|
# Remove the problematic generator from the available list for this session
|
|
if generator_id in available_generators:
|
|
available_generators.remove(generator_id)
|
|
|
|
# If we've exhausted all generators in this level, move to an adjacent level
|
|
if not available_generators:
|
|
fallback_level = max(
|
|
1, min(7, self.current_level + random.choice([-1, 1]))
|
|
)
|
|
available_generators = self.DIFFICULTY_LEVELS[fallback_level].copy()
|
|
|
|
# If all attempts fail, return a simple addition problem as fallback
|
|
return "What is $2 + 2$?", "4", 0
|
|
|
|
def record_performance(self, generator_id: int, is_correct: bool) -> None:
|
|
"""
|
|
Record the performance on a specific problem.
|
|
|
|
Args:
|
|
generator_id: The ID of the generator used
|
|
is_correct: Whether the answer was correct
|
|
"""
|
|
# Find which level this generator belongs to
|
|
level = None
|
|
for lvl, generator_ids in self.DIFFICULTY_LEVELS.items():
|
|
if generator_id in generator_ids:
|
|
level = lvl
|
|
break
|
|
|
|
if level is not None:
|
|
# Add the result to the performance history
|
|
self.performance_history[level].append(is_correct)
|
|
|
|
def get_success_rate(self, level: int) -> Optional[float]:
|
|
"""
|
|
Calculate the success rate for a specific level.
|
|
|
|
Args:
|
|
level: The difficulty level
|
|
|
|
Returns:
|
|
Success rate as a float between 0 and 1, or None if not enough data
|
|
"""
|
|
history = self.performance_history[level]
|
|
|
|
if len(history) < self.min_evaluations:
|
|
return None
|
|
|
|
# Calculate success rate from recent evaluations
|
|
recent_history = history[-self.min_evaluations :]
|
|
return sum(recent_history) / len(recent_history)
|
|
|
|
def should_advance(self) -> bool:
|
|
"""
|
|
Determine if the learner should advance to the next level.
|
|
|
|
Returns:
|
|
Boolean indicating whether to advance
|
|
"""
|
|
success_rate = self.get_success_rate(self.current_level)
|
|
|
|
# If not enough data or below threshold, don't advance
|
|
if success_rate is None or success_rate < self.progress_threshold:
|
|
return False
|
|
|
|
# Check if there's a next level to advance to
|
|
return self.current_level < max(self.DIFFICULTY_LEVELS.keys())
|
|
|
|
def advance_difficulty(self) -> bool:
|
|
"""
|
|
Advance to the next difficulty level if appropriate.
|
|
|
|
Returns:
|
|
Boolean indicating whether advancement occurred
|
|
"""
|
|
if self.should_advance():
|
|
self.current_level += 1
|
|
return True
|
|
return False
|
|
|
|
def get_current_level(self) -> int:
|
|
"""
|
|
Get the current difficulty level.
|
|
|
|
Returns:
|
|
Current level as an integer
|
|
"""
|
|
return self.current_level
|
|
|
|
def get_num_levels(self) -> int:
|
|
"""
|
|
Get the total number of difficulty levels.
|
|
|
|
Returns:
|
|
Total number of levels
|
|
"""
|
|
return len(self.DIFFICULTY_LEVELS)
|
|
|
|
def get_level_description(self, level: Optional[int] = None) -> str:
|
|
"""
|
|
Get a description of the specified difficulty level.
|
|
|
|
Args:
|
|
level: The level to describe (default: current level)
|
|
|
|
Returns:
|
|
String description of the level
|
|
"""
|
|
if level is None:
|
|
level = self.current_level
|
|
|
|
level_descriptions = {
|
|
1: "Basic arithmetic operations (addition, subtraction, multiplication, division)",
|
|
2: "Basic operations with fractions and pre-algebra",
|
|
3: "Basic geometry and more algebra",
|
|
4: "More advanced algebra and basic statistics",
|
|
5: "Vectors, matrices, and solid geometry",
|
|
6: "Advanced topics (calculus, statistics, computer science)",
|
|
7: "Most complex topics (complex numbers, advanced operations)",
|
|
}
|
|
|
|
return level_descriptions.get(
|
|
level, f"Custom level with IDs: {self.DIFFICULTY_LEVELS.get(level, [])}"
|
|
)
|
|
|
|
def reset(self, level: int = 1) -> None:
|
|
"""
|
|
Reset the curriculum to a specific level and clear performance history.
|
|
|
|
Args:
|
|
level: The level to reset to (default: 1)
|
|
"""
|
|
if level not in self.DIFFICULTY_LEVELS:
|
|
raise ValueError(
|
|
f"Invalid level: {level}. Available levels: {list(self.DIFFICULTY_LEVELS.keys())}"
|
|
)
|
|
|
|
self.current_level = level
|
|
self.performance_history = {lvl: [] for lvl in self.DIFFICULTY_LEVELS.keys()}
|
|
|
|
def get_generator_info(self) -> List[Dict[str, Any]]:
|
|
"""
|
|
Get information about all available generators.
|
|
|
|
Returns:
|
|
List of dictionaries containing generator information
|
|
"""
|
|
generators = []
|
|
gen_list = mathgenerator.getGenList()
|
|
|
|
for gen in gen_list:
|
|
# Find which level this generator belongs to
|
|
level = None
|
|
for lvl, generator_ids in self.DIFFICULTY_LEVELS.items():
|
|
if gen[0] in generator_ids:
|
|
level = lvl
|
|
break
|
|
|
|
generators.append(
|
|
{
|
|
"id": gen[0],
|
|
"name": gen[1],
|
|
"function": gen[3],
|
|
"subject": gen[4],
|
|
"params": gen[5],
|
|
"difficulty_level": level,
|
|
}
|
|
)
|
|
|
|
return generators
|